개요
- JDBC(Java Database Connectivity) - 1990 중반. 자바 프로그래밍 언어의 일부
- JdbcTemplate - 2000년 초. Spring Framework의 일부. Spring과 함께 등장
- JPA(Java Persistence API) - 2000년대 중반. 자바 EE 표준. ORM(Object-Relational Mapping)을 위한 API 제공. Hibernate는 JPA의 구현체
- Spring Data JPA - 2010년 초. Spring Data 프로젝트의 일부. JPA를 더 쉽게 사용 리포지토리 계층 쉽게 구현하는 추상화 제공
- Spring Data JDBC - 2010년 후반. Spring Data JDBC. JPA의 복잡성을 줄이고자 등장. 도메인 중심 설계 촉진
Data JDBC가 최신이니까 제일 좋은가?
- 꼭 그런건 아니다. 추상화 수준으로 보면 JPA의 추상화 수준이 더 높다.
- JPA는 ORM 제공을 주 목적으로 하고
- JDBC는 도메인 중심 설계 지원을 주 목적으로 한다.
코드 비교
JDBC
Interface
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
repository (본체)
- 직접 따로 만든 interface를 상속함
- repository에 직접 구현된 메소드들.
- 메소드안에서 db와 연결, 쿼리 보내기, 받은 결과 처리 하는 코드를 매번 해야 함
- 쿼리 결과를 Entity의 메소드를 직접 호출해 처리함.
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection(); //1.connection
//2. sql 셋팅
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys(); //3.result 받음
member.setId(rs.getLong(1));
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
//연결 종료
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findById(Long id) {
//구현
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
//...
rs.close();
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
Entity
- 객체
- Lombock쓰면 getter setter 따로 구현 안해도 됨.
public class Member {
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;
}
JDBC template
- 귀찮은 연결과, 쿼리문 작성을 해결
- 의존성 관리
implementation 'org.springframework:spring-jdbc:5.3.10' //버전 조정 필요
repository
- 복잡했던 connect, query보내기 과정을 없앰 (Query도 없애줌)
- =ORM의 시작.
- 여전히 repository interface 를 직접 구현함.
- 여전히 member의 메소드를 직접 호출해서 로직을 처리함.
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();
}
}
JPA
- Entity Manager의 도입
- 의존성 관리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Entity
- annotation 활용으로 id 등 자동 생성
- 롬복까지 적용한 코드
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Repository
- EntityManager 를 통해 @Enitity로 등록된 Entity를 더 쉽게 관리
- member의 getter setter를 직접 호출하지 않음
- EntityManager 능력 밖의 코드는 query를 직접 써서 처리
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em; //매니저
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
public Member save(Member member) { //일반적으로 이렇게 쉬움
em.persist(member); //member의 getter, setter는 매니저가 알아서 호출해줌.
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)
}
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();
}
}
Service
@Transactional
public class MemberService {}
Spring Data JPA
- 기본으로 쓰는 메소드는 선언조차 사치임
- 의존성 관리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.0' //버전 조정 필요
Repository
- 현재까지 ORM 끝판왕
- 구현체가 아니라 Interface임
- MemberRepository 인터페이스를 직접 구현하지 않으므로 해당 interface는 이제 없어도 됨. (제약을 주기 위해 extends 할 수는 있음)
public interface SpringDataJpaMemberRepository extends JpaRepository<Member,
Long>, MemberRepository {
Optional<Member> findByName(String name);
}
- 대부분을 JpaRepository가 알아서 해줌
- 기본 제공 메서드
- persist
- merge
- remove
- find - 주어진 기본 키로 엔티티를 찾음
- 추가 메소드
- findByName같은 명명 규칙에 맞게 선언만 하면, 그 기능을 JpaRepository가 알아서 작성함.
- 위 Interface의 구현체 자체가 필요 없음. 갓갓.
Spring Data JDBC
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc:2.6.0' // Spring Boot 버전에 맞게 조정
Repository
public interface BookRepository extends CrudRepository<Book, Long> {
// 여기에 필요한 쿼리 메소드를 추가할 수 있습니다.
}
Entity
@Getter
@Setter
public class Member { private Long id; private String name; }
- 특별한 클래스 타입인 record (JAVA 16부터 도입)로도 나타낼 수 있음
public record Member(@Id Long id, String name) { }
- 자동으로 final 선언
- getter/setter가 필요 없음. 자동 접근 가능
- 이외 자동 생성되는 함수들
- equals()
- hashcode()
- toString()
- 위 형태는 불변 객체라서 ORM 위주인 JPA에는 적합하지 않음
- ORM의 변경 감지(Dirty checking), 연관 관리, 지연로딩(lazy loading) 등의 기능을 쓰려면 변경 가능한 객체여야 함.
- JDBC 는 집합 루트 중심으로 객체를 다룬다. record가 더 적합할 수 있음