바로 포스팅을 하겠다!
스프링 DB 접근 기술이 총 4가지로 구성되어 있는 것 같다!
1. 순수 JDBC
2. Spring JDBC Template
3. JPA
4. Spring Data JPA
1. 순수 JDBC
순수 JDBC 방식은 20여년 전에 쓰였던 방식이라고 한다
정신건강을 위해 참고만 하고 넘어가라고 하셨는데 나는 이번학기에 이 방법으로 JDBC 방식을 배우고 실습을 했었는데 뭔가 당황스러웠다..
참고하라고 하시니 패스하고 핵심만 쓰자면
순수 JDBC 방식과 Spring JDBC Template 방식에 사용되는
DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체이다.
스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어두어 DI를 받을 수 있다.
쉽게 말하자면 DB를 사용하려면 기본적으로 DataSource의 객체를 사용해야 한다
2. Spring JDBC Template
순수 JDBC 방식과 동일한 환경설정이며, 이 방식과 다른 점은 반복 코드를 대부분 제거해준다는 장점이 있다.
하지만 SQL은 직접 작성해야 한다
아래 JPA를 사용하게 되면 SQL 사용이 매우 적거나 없다고 봐야한다
하지만 JDBC Template를 사용하는 경우도 있기에 이도 놓치지 말고 공부해야 한다
JdbcTemplateMemberRepository.class
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 Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
@Override public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
return (rs, rowNum) -> {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
};
}
}
이렇게 바꾼 후 JDBC Template를 사용하도록 스프링 설정을 변경해야 한다
SpringConfig.class
@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 JdbcTemplateMemberRepository(dataSource);
}
}
Template를 사용하면 save와 같은 기능은 JDBC Template에 있는 SimpleJdbcInsert를 사용하면 매우 간단하게 저장할 수 있다
근데 나는 오히려 그냥 쿼리가 더 쉬워보였던 건 기분 탓인가.. 싶었다
순수 JDBC 코드가 훨씬 길긴한데 그게 익숙해서 그런 것 같다
하지만 나머지 select 구문이 있는 조회 기능은 매우 코드가 단순하여 좋았다
조회할 때 DB에 매핑해주는 RowMapper<>를 구현해야 했기에 조금 까다로울 수 있지만 이것만 구현하면 조회는 거의 1, 2줄이면 끝나기 때문에 앞으로 Spring을 할 때면 이 방법을 유용하게 사용할 듯 싶다!
3. JPA
JPA 부터는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA까 직접 만들어서 실행해준다.
그렇게 되면 개발자는 개발 생산성을 크게 높일 수 있다!
JPA를 사용하면 객체 중심의 설계로 패러다임을 전환할 수 있다고 한다.
JPA는 EntityManager를 사용하여 DB에 접근할 수 있다
JPA 리포지토리를 구현하기 전에 Member 클래스의 어노테이션을 등록해야 한다
Member.class
@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에서 DB를 자동으로 등록해주기 때문에 Member id에 자동으로 증가시킬 것을 선언해줘야 한다
다음은 JpaMemberRepository이다
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 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();
}
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class).getResultList();
}
}
코드 길이만 봐도 JDBC Template과 비교했을 때 확연하게 줄은 것을 알 수 있다
우선 SQL 쿼리문이 아예 없다 JPQL의 쿼리문은 조금은 있지만 매우 단순하다
save 메서드에서 em.persis(member)에서 persist는 영구저장한다는 의미이다.
단순 함수라고 생각하면 된다!
아까 설정한 id 같은 경우에는 기본키이기 때문에 fine() 함수로 검색할 수 있다
다음 findByName()과 findAll() 같은 경우에는 JPQL 쿼리문이 사용되었는데
상대적으로 복잡한 findByName에 대해서 설명을 남기겠다
public Optional findByName(String name) {
List result = em.createQuery("select m from Member m where m.name = :name", Member.class) .setParameter("name", name) .getResultList();
return result.stream().findAny();
}
em.createQuery()문으로 JPQL 쿼리문을 사용할 수 있다. 안의 문장을 들여보면
select m from Member m where m.name = :name"이 있다
DB에 있는 name이 name인 경우 테이블 Member의 m 객체로 검색한다는 의미이다
m.name은 DB에 있는 name을 뜻하며 :name은 쉽게 '?'라고 생각하면 된다
여기서 뒤에 .setParameter("name", name)이 나오는데
"name"은 아까 쓴 :name을 의미하고 뒤에 나오는 name은 파라미터로 받은 String name이다
여기서 Parameter 값을 setting한다고 생각하면 된다
위에서 JPQL 쿼리문에는 임의로 값을 지정한거고 setParameter을 통해 값을 주입하는 느낌이다
그렇게 List 값으로 반환한다.
JPA를 사용할 경우 어노테이션 @Transactional을 사용해야 한다
JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 하며 여기서는 MemberService.class에 어노테이션을 설정하였다
MemberService.class
@Transactional
public class MemberService {}
JPA를 사용하기 위해 스프링 설정 변경이 필요하다
SpringConfig.class
@Configuration
public class SpringConfig {
private final DataSource dataSource;
private final EntityManager em;
public SpringConfig(DataSource dataSource, EntityManager em) {
this.dataSource = dataSource;
this.em = em;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean public MemberRepository memberRepository() {
return new JpaMemberRepository(em);
}
}
4. Spring Data JPA
강의를 들으면서 이 방법은 진짜 혁명이다 라고 생각이 들긴 했다
이 방법은 리포지토리에 구현 클래스마저 없이 인터페이스 만으로 개발을 할 수 있는 방식이다
기본적인 CRUD 기능을 스프링 데이터 JPA가 모두 제공한다고 생각하면 된다.
리포지토리가 얼마나 주는지 한눈에 보일 것이다
SpringDataJpaMemberRepository.interface
public interface SpringDataJpaMemberRepository extends JpaRepository, MemberRepository {
Optional findByName(String name);
}
클래스도 아닌 인터페이스로 코드가 구현이 되었다
특별한 점은 JpaRepository를 상속받은 점이다
이 Repository안에 기본적인 CRUD가 구현되어 있다
Name(이름) 같은 경우에는 도메인 마다 다 다를 수 있기 때문에 JpaRepository에 따로 없다고 한다
그렇지만 이렇게 메서드를 써주면 자동으로 구현이 되는 느낌인 것 같다
Spring Data JPA를 사용하기 위한 스프링 설정 변경이다
SpringConfig.class
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
여기서 또 특이한 점은 원래 @Bean의 return 값에 생성자로 받아 리턴했던 것 같은데
Spring Data JPA에서는 위에 객체를 만들어서 객체를 리턴하였다
아래는 Spring Data JPA의 구성도이다
JpaRepository를 상속받아 CrudRepository에서 기본적인 CRUD가 구현되어 있고, PagingAndSortingRepository에서 검색하는 부분도 구현이 되어있다
사용자는 이를 이용만 하면 되는 느낌이다
그렇지만 Spring Data JPA를 사용하려면 JPA와 전체적인 흐름을 다 이해해야 할 것 같다
코드 자체는 너무 단순하여 코드상으로는 너무 좋았다
저번 포스팅 강의를 듣고 바로 이어 들었는데 여기서 부터 정말 쉽지 않았다
20년 전에 했던 방식으로도 설명해주셨는데 어째서인지 나는 이 방법으로 DB를 접근하고 있었다
선생님께서는 정신 건강을 위해 참고로만 보라고 하셨는데.. 이제껏 그런 방법으로만 봐서 그런지 그 방법이 눈에 익숙하여 계속 보고있었다
몰랐던 JPA 방식을 알게되니까 Spring이 많이 쓰고 유명한 이유를 알 거 같았다
JPA에 대해서 시간이 나면 포스팅을 작성하고 싶다.
이제 시험기간이라 블로그 포스팅이 쉽지 않겠지만 Spring 공부는 계속 꾸준히 해야할 것 같다
'Spring Boot(스프링 부트)' 카테고리의 다른 글
Spring Boot(스프링 부트) > 스프링 빈과 의존관계 (1) | 2024.12.02 |
---|---|
레이어드 아키텍처 패턴(Layered Architecture) (2) | 2024.11.17 |
Spring Boot(스프링 부트) > 회원 관리 예제 (3) | 2024.11.17 |
URI vs. URL (1) | 2024.11.10 |
Spring Boot(스프링 부트) > 정적 컨텐츠 vs. MVC vs. API (4) | 2024.11.09 |