JPA란?
JPA (Java Persistence API) 자바 진영의 ORM(Object Relational Mapping) 기술의 표준이다.
ORM을 사용하여 DB에 지속적으로 많은 양의 데이터를 관리하기 위한 API를 의미한다.
💡 ORM? (Object Relational Mapping)
- ORM이란 데이터를 Object 타입에서 Relational 타입으로 또는 반대로 바꾸는 기술이다.
- Object를 DB 내의 데이터와 매핑하는 것이 가장 중요한 특징이다.
- 이에 개발자들은 매핑시 데이터, 데이터 유형, 테이블 자체의 엔티티 및 엔티티와의 관계를 고려해야한다.
- 인터페이스이기 때문에 구현체가 존재한다.
- hibernate, EclipseLink... 등등
💡 Spring Data JPA?
- 우리가 Spring을 쓰면서 사용하는 Spring Data JPA 는 hibernate 같은 구현체를
더 쓰기 쉽게 추상화 한 것이다. - Repository 인터페이스를 제공하여 정해진 규칙대로 메소드를 입력하면, Spring이 알아서
해당 메소드 이름에 적합한 쿼리를 날리는 구현체를 만들어 Bean으로 등록해준다.
JDBC API의 문제점
Java를 사용하면서 DB와 연동을 해보았다면 거의 JDBC API를 이용하여 개발을 해보았을 것이다.
- JDBC API를 활용한 개발에서는 어떤 문제가 있었을까?
- DB의 데이터를 받아올 수있는 객체를 만들어야한다.
- Entity 생성 DTO를 통한 getter, setter 생성
- DB에 접근할 수 있는 DAO를 만들어야한다.
- DB 접근 양식 작성
- SQL 작성 및 쿼리문 실행
- DB의 데이터를 받아올 수있는 객체를 만들어야한다.
위를 예시로 하여 평소에 사용하던 JDBC API를 간단히 작성해보자.
**저장**
String insertSQL = "INSERT INTO MEMBER VALUES(?, ?, ?)";
pstsmt.setString(1, member.getId());
pstsmt.setString(2, member.getName());
// pstsmt.setString(3, member.getTel());
//////////////////////////////////////////////////////////
**조회**
String selectSql = "SELECT * FROM MEMBER WHERE ID = ?";
String id = rs.getString("ID");
String name = rs.getString("NAME");
// String tel = rs.getString("TEL");
member.setId(id);
member.setName(id);
// member.setTel(id);
- 개발을 끝낸 후 만약 테이블이 변경되게 되면?
- 객체 수정
- 엔티티에서 Tel이 추가 된다면 주석 처리를 해둔 부분을 작성해야한다.
- 데이터를 새로 전달해야하므로 get, set을 설정해주어야한다.
- DAO 수정
- 쿼리문 수정
- 테이블이 바뀌게 되었으므로 저장과 조회에 대한 쿼리문도 수정해야한다.
- 쿼리문 수정
- 객체 수정
⇒ 위와 같은 상황이 무수하게 반복되다 보니 SQL문을 직접 다룰 때는 문제가 발생할 수 밖에 없다.
이런 문제를 JPA릍 통해 해결해볼 수 있다.
**저장**
jpa.persist();
/////////////////////////////////////////////////
**조회**
String memberId = "id";
Member member = jpa.find(Member.class, memberId);
쿼리문을 작성하지 않아도 되고, 그저 JPA가 객체와의 매핑정보를 확인하고 쿼리문을 날린다.
이는 저장과 단순한 조회 뿐만 아니라 수정, 연관객체 조회까지 해낼 수 있다.
어떻게 이런 일이 가능한지는 차근차근 알아보자.
패러다임의 불일치
객체지향 프로그래밍은 상속, 다형성, 추상화, 캡슐화 등을 이용하여 복잡한 프로그램을 제어할 수 있는 다양한 장치들을 제공한다.
그에비해 관계형 데이터베이스는 그러한 개념이 없으며 데이터 중심으로 설계가 되어있다.
이렇게 둘의 목적이 다르기 때문에 기능과 표현 방법이 달라지게 되는데 이것을 패러다임의 불일치라고 부른다.
문제가 되는 패러다임들에 대해서 살펴보자.
상속
객체지향 프로그래밍에서는 객체 간의 상속을 통해 코드의 재사용 등의 이점을 통해 개발시간을 단축시킬 수 있다.
관계형 데이터베이스에서는 이런 기능이 없지만 슈퍼타입, 서브타입 관계를 통해 이와 비슷하게 설계를 할 수 있다.
abstract class Item {
Long Id;
String name;
int price;
}
class Book extends Item {
String artist;
}
만약 Book 객체에 데이터를 집어넣으려면 Item에 대한 SQL도 작성해야만 한다.
INSERT INTO ITEM VALUES (1000, "책이름", 35000);
INSERT INTO BOOK VALUES ("작가이름");
지금에야 간단해서 상관이 없지만 후에 복잡한 테이블을 관리할 때는 작성해야할 코드량이 어마어마 해질 것이다.
JPA의 해결
jpa.persist(book);
을 이용하여 여러 SQL문을 작성해야하는 불편함을 해결할 수 있다.
물론 조회할 때에도 자동으로 JOIN을 통해 결과값을 가져오게된다.
연관관계 (참조, 객체 테이블 모델링)
객체지향 프로그래밍에서는 참조를 사용하여 다른 객체와 연관관계를 설정할 수 있다.
관계형 데이터베이스에서는 외래키를 사용하여 이를 설정해야한다.
이에 객체를 테이블에 맞춰서 모델링해서 사용하기도한다.
class Member {
String id; // 기본키
Long team Id; // 외래키
String username;
}
class Team {
Long id; // 기본키
String name;
}
위 처럼 테이블 중심적으로 객체를 설계하게 되면, 연관된 객체를 통해서 다른 객체를 조회할 수 없게된다.
이는 객체지향적인 설계에서 벗어나는 경우이기 때문에 아래와 같이 참조를 이용해서 연관관계를 설정해 사용해야한다.
class Member {
String id; // 기본키
Team team; // 참조를 통한 연관관계 설정
String username;
Team getTeam() {
return team;
}
}
class Team {
Long id; // 기본키
String name;
}
하지만 위와같이 객체를 생성하여도 결국에는 문제가 생기게된다.
객체에서는 객체간의 연관관계를 설정하지만, 테이블에서는 외래키와의 연관관계가 설정되어있기 때문이다.
이러한 변환 작업은 결국에 개발자가 해주어야한다.
ex) member.getTeam().getid();
와 같은 외래키 매핑
JPA의 해결
member.setTeam(team);
을 통해서 연관관계를 설정해주고, jpa.persist(member);
를 통해서 member 정보와 연관관계를 한 번에 저장할 수 있다.
연관된 객체를 참조할 때 외래키를 참조하는 일을 JPA가 대신 해주게되는 것이다.
객체 그래프 탐색
객체 그래프 탐색은 연관관계에서 이어지는 부분이다.
여러 연관관계가 얽혀있다고 할 때, SQL을 사용한다면 탐색할 수 있는 영역이 제한되게 된다.
탐색할 수 있는 영역이 객체 그래프라고 할 수 있다.
새로운 연관관계가 생긴다면 개발자가 일일히 이어주어야만 한다.
그렇지 않으면 언제 관계가 끊길지 모르는데 기능을 사용할 수만은 없는 노릇이기 때문이다.
ex) memberDAO.getMember();/.getMemberWithTeam();/.getMemberWithOrderWithDelivery();
등
JPA의 해결
위에서 설정했던 것 처럼 참조만 설정해두어도 객체 그래프를 마음껏 탐색할 수 있게된다.
또한 JPA를 사용하면 지연로딩을 통해서 쿼리문 실행을 실제 객체 사용 전까지 미룰 수 있다.
- 지연로딩을 사용하는 것이 각각 테이블을 하나씩 조회하는 것 보다 매우 효과적으로 사용 될 수 있게 도와준다.
비교
데이터베이스는 기본키를 기준으로 각 데이터(row)를 구분한다.
객체는 동일성 (==, 주소 값 비교) 과 동등성 (.equals(), 값 비교)을 통해 비교한다.
데이터베이스에서 데이터를 객체로 가져와 비교를 해보는 예제가 있다.
class MemberDAO {
public Member getMember(String memberId) {
String sql = "SELCT * FROM MEMBER WHERE ID = ?";
pstmt.execute(sql, memberId);
return new Member(....);
}
}
//------------------------------------------------------
String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);
if (member1 == member){
System.out.println("같은 값");
}else{
System.out.println("다른 값");
}
위 코드의 결과는 서로 다른 값으로 출력된다는 것이다.
분명히 같은 SQL을 실행하여 값을 return 받았을 텐데 객체가 동일성 비교를 통해 확인하면
서로 다른 값으로 인식이 되어버린다.
JPA의 해결
JPA는 같은 트랜잭션(SQL의 실행단위) 일 때 같은 객체가 조회되는 것을 보장해준다.
- 아래 두 값은 같다고 출력된다.
Member member1 = jpa.find(Member.class, memberId);
Member member2 = jpa.find(Member.class, memberId);
이와 같이 다양한 패러다임의 불일치 사례를 JPA를 통해 손쉽게 해결할 수 있다.
정리
JPA (Java Persistence API)는 자바 진영의 ORM(Object Relational Mapping) 기술의 표준이다.
JPA는 JDBC만을 사용했을 때의 생기는 문제점들을 해결할 수 있게 도와준다.
장점으로 정리해보자면 아래와 같다.
- 생산성
- 반복적인 코드와 SQL 작성을 줄여 개발자가 좀 더 객체지향적인 설계를 할 수 있게 돕는다.
- 유지보수
- SQL에 의존적인 개발을 하게되면 코드 하나를 수정시에도 여러 SQL과 결과를 매핑하기 위한 코드들을 수정했어야 한다.
- JPA는 이를 해결해주며 유지보수 측면에서도 유용하다.
- 패러다임의 불일치 해결
- 위에서 설명했던 내용들을 해결할 수 있다.
- 성능
- JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화를 제공한다.
- 데이터 접근 추상화와 벤더 독립성
- JPA는 추상화된 데이터 접근 계층을 제공해 애플리케이션이 특정 데이터베이스 기술(MySQL, Oracle 등등)에 종속되지 않도록 해준다.
- 표준
- JPA는 자바의 ORM 기술 표준이다.
이를 통해서 우리는 JPA를 사용하면 어떤 이점이 있고, 어떻게 도움이 되는지 알게 되었다.
앞으로는 더 자세한 활용법에 대해서 공부하도록 해야겠다.