자바 ORM 표준 JPA 프로그래밍을 학습한 내용을 정리합니다.
관계형 데이터베이스는 가장 대중적이고, 신뢰할 만한 안전한 데이터 저장소다.
→ 그래서 자바로 개발하는 애플리케이션은 대부분 관계형 데이터베이스를 사용한다.
SQL을 직접 다룰 때의 문제점을 알아보자.
public class Member {
private String memberId;
private Stirng name;
}
public class MemberDAO {
public Member find(String memberId) {...}
}
MemberDAO의
find()
메소드로 회원 조회 기능을 개발하는 순서
회원 조회용 SQL을 작성
→ SELECT member_id, name FROM member WHERE member_id = ?
JDBC API를 사용해 SQL을 실행
→ ResultSet rs = stmt.executeQuery(sql);
String memberId = rs.getString("member_id");
String name = rs.getString("name");
Member member = new Member();
member.setMemberId(memberId);
member.setName(name);
회원 등록 기능을 추가했다고 가정
public class MemberDAO {
public Member find(String memberId) {...}
public void save(Member member) {...}
}
회원 등록 기능을 개발하는 과정
회원 등록용 SQL 작성
→ String sql = "INSERT INTO member(member_id, name) VALUES(?, ?)";
회원 객체의 값을 꺼내 등록 SQL에 전달
pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
pstmt.executeUpdate(sql);
데이터베이스는 객체 구조와는 다르게 데이터 중심의 구조를 가진다.
→따라서, 객체를 데이터베이스에 직접 저장하거나 조회할 수가 없다!→ 개발자는 SQL과 JDBC API를 사용해 변환 작업을 직접 해주어야 한다.
위에서 MemberDAO를 완성했는데, 갑자기 회원의 연락처도 저장해달라는 요구사항이 발생
public class Member {
private String memberId;
private Stirng name;
private String tel; //추가
}
String sql = "INSERT INTO member(member_id, name, tel) VALUES (?, ?, ?)";
pstmt.setString(3, member.getTel());
현재까지 상황 정리
- 데이터 접근 계층을 사용해 SQL을 숨겨도 어쩔 수 없이 DAO를 통해 어떤 SQL이 실행되는지 확인해야 한다.
→ 이는 진정한 의미의 계층 분할이 아니다.
→ 물리적으로 SQL과 JDBC API를 데이터 접근 계층에 숨기는 데에는 성공했을지 몰라도,
→ 논리적으로는 엔티티와 아주 강한 의존관계를 가지고 있다.
→ 이것이 변경사항이 생겼을 때 대부분의 코드를 변경해야 하는 문제를 불러 일으킨다.
JPA가 제공하는 CRUD API를 간단히 알아보자!
jpa.persist(member);
String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); //조회
Member member = jpa.find(Member.class, memberId);
member.setName("이름변경");
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); //연관된 객체 조회
객체지향과 관계형 데이터베이스의 패러다임 불일치 문제를 살펴보자!
슈퍼타입-서브타입
관계를 사용해 상속과 유사하게 만들 수 있다.DTYPE
컬럼을 사용해 어떤 자식 테이블과 관계있는지 나타낸다.DTYPE = MOVIE
이면 영화 테이블과 관련있음JPA와 상속
JPA를 이용해 객체를 저장하는 과정
Item을 상속한 Album 저장
→ jpa.persist(album);
JPA는 SQL을 실행해 객체를 ITEM, ALBUM 두 테이블에 나누어 저장
INSERT INTO ITEM ...
INSERT INTO ALBUM ...
String albumId = "id001";
Album album = jpa.find(Album.class, albumId);
SELECT I.*, A.*
FROM item I
JOIN album A ON I.item_id = A.item_id
참조를 사용하는 객체 ←→ 외래키를 사용하는 관계형 데이터베이스
member.getTeam();
을 이용해 참조 필드에 접근 가능class Member {
Team team;
Team getTeam() { return team; }
}
SELECT M.*, T.*
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID
class Member {
String id; //MEMBER_ID 컬럼
Long teamId; //TEAM_ID FK 컬럼
String username; //USERNAME 컬럼
}
class Team {
Long id; //TEAM_ID PK 사용
String name; //NAME 컬럼 사용
}
class Member {
String id; //MEMBER_ID 컬럼
**Team team; //참조로 연관관계를 맺는다.**
String username; //USERNAME 컬럼
}
class Team {
Long id; //TEAM_ID PK 사용
String name; //NAME 컬럼 사용
}
public Member find(String memberId) {
//SQL 실행
...
Member member = new Member();
//DB에서 조회한 회원 관련 정보를 모두 입력
Team team = new Team();
...
//DB에서 조회한 팀 관련 정보를 모두 입력
//회원과 팀 관계 설정
member.setTeam(team);
return member;
}
member.setTeam(team); //회원과 팀 연관관계 설정
jpa.persist(member); //회원과 연관관계 함께 저장
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
Team team = member.getTeam();
와 같이 참조를 이용한다.SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다.
→ 이는 객체지향 개발자에겐 너무 큰 제약!
class MemberService {
public void process() {
Member member = memberDAO.find(memberId);
member.getTeam();
member.getOrder().getDelivery(); // ???
}
}
member.getOrder().getOrderItem()........ //자유로운 객체 그래프 탐색
class Member {
private Order order;
public Order getOrder() {
return order;
}
}
//처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDaate(); //Order를 사용하는 시점에 SELECT ORDER SQL
identity
) 비교와 동등성(equality
) 비교가 있다.class Member {
public Member getMember(String memberId) {
String sql = "SELECT .......";
//JDBC API, SQL 실행
**return new Member(...); //실행할 때마다 새로운 객체를 생성**
}
}
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //다르다
기본 키 값이 같은 회원 객체를 조회했기 때문에 데이터베이스에서는 같은 로우를 조회한다.
하지만, 객체 측면에서 볼 때 (메소드를 실행할 때마다 새로운 객체를 반환했으므로) 다른 객체다.여기서도 패러다임의 불일치 문제를 해결해야 한다!
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
member1 == member2; //같다
Java Persistence API
)는 자바 진영의 ORM 기술 표준이다.Object Relational Mapping
)은 무엇일까?ORM 프레임워크는 다양한 패러다임의 불일치 문제들도 해결한다.
- 덕분에 객체 측면에서는 정교한 객체 모델링을 할 수 있고, 관계형 DB는 데이터베이스에 맞게 설게하면 된다.
- 개발자는 데이터 중심인 관계형 데이터베이스를 사용해도 객체지향 애플리케이션 개발에 집중할 수 있다!
과거 자바 진영
- 과거에 자바는 엔터프라이즈 자바 빈즈(EJB)라는 표준 기술을 만들었다.
- 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었다.
- 하지만, 너무 복잡하고 성숙도가 떨어져 자바 엔터프라이즈(J2EE) 애플리케이션 서버에서만 동작함
- 이때 하이버네이트가 등장! → 실용적이고 가벼움!
- 이후, EJB 3.0에서 하이버네이트를 기반으로 새로운 자바 ORM을 만듬 → 이게 JPA
![](https://velog.velcdn.com/images/dongvelop/post/23f3cb93-0c04-47b0-b3ce-345db7045866/image.png)
jpa.persist(member);
데이터 접근 추상화와 벤더 독립성
Q&A
- JPA는 학습 곡선이 높다고 하던데요?
- 맞다. JPA를 사용하려면? → 객체와 관계형 데이터베이스의 매핑을 학습한 후 이해해야 한다.
- 또한 JPA의 핵심인 영속성 컨텍스트에 대한 이해가 부족하면 SQL을 직접 사용하는 것보다 못한 상황이 벌어질 수 있다.
- 사실 JPA가 근본적으로 어려운 이유는 ORM이 객체지향과 관계형 데이터베이스라는 두 기둥 위에 있기 때문이다!
- 이 둘의 기초가 부족하면 어려울 수밖에 없다.