Spring 스프링 입문 - 6. 스프링 DB 접근 기술
스프링 DB 접근 기술
H2 데이터베이스 설치
- 개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공
순수 Jdbc
- Jdbc 리포지토리 구현
- 스프링 설정 변경
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
@Configuration public class SpringConfig { private final DataSource dataSource; public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; } @Bean public MemberService memberService() { return new MemberService(memberRepository()); } @Bean public MemberRepository memberRepository() { // return new MemoryMemberRepository(); return new JdbcMemberRepository(dataSource); } }
private final DataSource dataSource;
- DataSource는 데이터베이스 커넥션을 획득할때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링빈으로 만들어둔다. 그래서 DI를 받을 수 있다.
- 개방-폐쇠 원칙(OCP, Open-Closed Principle)
- 확장에는 열려있고, 수정, 변경에는 닫혀있다.
- 스프링의 DI(Dependency Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
스프링 통합 테스트
- 스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
@SpringBootTest @Transactional class MemberServiceIntegrationTest { @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Test public void 회원가입() throws Exception { //Given Member member = new Member(); member.setName("hello"); //When Long saveId = memberService.join(member); //Then Member findMember = memberRepository.findById(saveId).get(); assertEquals(member.getName(), findMember.getName()); } @Test public void 중복_회원_예외() throws Exception { //Given Member member1 = new Member(); member1.setName("spring"); Member member2 = new Member(); member2.setName("spring"); //When memberService.join(member1); IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));//예외가 발생해야 한다. assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."); } }
@SpringBootTest
: 스프링 컨테이너와 테스트를 함께 실행한다.@Transactional
: 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
스프링 JdbcTemplate
- 순수 Jdbc와 동일한 환경설정을 하면 된다.
- 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
public class JdbcTemplateMemberRepository implements MemberRepository { private final JdbcTemplate jdbcTemplate; public JdbcTemplateMemberRepository(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } @Override public Member save(Member member) { SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate); jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); Map<String, Object> parameters = new HashMap<>(); parameters.put("name", member.getName()); Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); member.setId(key.longValue()); return member; } @Override public Optional<Member> findById(Long id) { List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id); return result.stream().findAny(); } @Override public List<Member> findAll() { return jdbcTemplate.query("select * from member", memberRowMapper()); } @Override public Optional<Member> findByName(String name) { List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name); return result.stream().findAny(); } private RowMapper<Member> memberRowMapper() { return (rs, rowNum) -> { Member member = new Member(); member.setId(rs.getLong("id")); member.setName(rs.getString("name")); return member; }; } }
JPA
- JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
- JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
- JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
JPA 엔티티 매핑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
JPA 회원 리포지토리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
public Member save(Member member) {
em.persist(member);
return member;
}
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
return result.stream().findAny();
}
}
- JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
스프링 데이터 JPA
- 스프링 부트와 JPA만 사용해도 개발 생산성이 정말 많이 증가하고, 개발해야할 코드도 확연히 줄어든다.
- 여기에 스프링 데이터 JPA를 사용하면, 기존의 한계를 넘어 마치 마법처럼, 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공한다.
- 스프링 부트와 JPA라는 기반 위에, 스프링 데이터 JPA라는 환상적인 프레임워크를 더하면 개발이 정말 즐거워진다.
- 지금까지 조금이라도 단순하고 반복이라 생각했던 개발 코드들이 확연하게 줄어든다.
- 따라서 개발자는 핵심 비즈니스 로직을 개발하는데, 집중할 수 있다.
- 실무에서 관계형 데이터베이스를 사용한다면 스프링 데이터 JPA는 이제 선택이 아니라 필수이다.
스프링 데이터 JPA 회원 리포지토리
1 2 3
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository { Optional<Member> findByName(String name); }
- 스프링 데이터 JPA가
SpringDataJpaMemberRepository
를 스프링 빈으로 자동 등록해준다스프링 데이터 JPA 제공 클래스
스프링 데이터 JPA 제공 기능
- 인터페이스를 통한 기본적인 CRUD
findByName()
,findByEmail()
처럼 메서드 이름 만으로 조회 기능 제공- 페이징 기능 자동 제공
참고: 실무에서는 JPA와 스프링 데이터 JPA를 기본으로 사용하고, 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용하면 된다. Querydsl을 사용하면 쿼리도 자바 코드로 안전하게 작성할 수 있고, 동적 쿼리도 편리하게 작성할 수 있다. 이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 앞서 학습한 스프링 JdbcTemplate를 사용하면 된다.
References: 김영한 - [스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술]
This post is licensed under CC BY 4.0 by the author.