먼저 객체와 관계형 DB에 대해 알아보자!
// 객체
public class User { // User라는 객체
private String name; // 속성(필드)
private String age; // User안에 있는 속성
public void 메서드명(매개변수) {
...
}
}
// RDB
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
age INT
);
insert User(), selectUser(), updateUser, delete User() SQL과 메서드 작성insert Post(), selectPost() ... SQL과 메서드 작성
이 문제를 ORM이 해결해준다.
User객체를 넘겨줄 테니까, 네가 알아서 INSERE문을 만들어서 넣어줘" 한다.save(), findAll())를 호출하기만 하면 된다.
이제 RDB와 객체지향의 패러다임 불일치 문제에 대해 알아보자!
이때 저장할 때와 조회할 때 패러다임 불일치 문제가 발생한다.
CardPayment 객체 하나를 저장하고 싶은데, DB입장에서는 payment 테이블에 넎고 card 테이블에도 나눠서 넣어야 한다.(쿼리 2번 작성)JOIN을 해야 한다.그래서 관계형 DB에서는 Data를 슈퍼타입, 서브타입 관계로 설정한다.

ORM에서는 이 슈퍼타입/서브타입 관계를 실제 DB테이블로 만들 때 보통 3가지 전략을 시도한다.
i. 조인 전략
각각 테이블로 만들고 조인으로 구성
특징 : 가장 정석적이고 데이터가 깔끔하다.

Tutor 저장 : 위 이미지를 보면 Tutor테이블에 데이터를 추가하려고 한다면 튜터에만 데이터를 INSERT하는게 아니라 Person에도 INSERT해야 하는 문제가 생긴다.

Tutor 조회 : 위 이미지처럼 튜터 테이블과 펄슨 테이블이 두 개로 나눠져 있는데 연관관계를 맺어져 있어 SELECT를 할 때 JOIN을 사용해서 외래키로 접근해 person테이블과 tutor테이블을 한꺼번에 조회할 수 있게끔 한다.
Join문을 사용하면 까다롭기 때문에 DB에 저장할 객체는 상속 관계를 사용하지 않는다.
ii. 연관관계
테이블의 연관관계는 외래키를 사용한다.

// DB 중심 객체 설계
public class Tutor {
private Long id; // PK
private Long companyId; // FK
private String name;
}
public class Company {
private Long id; // PK
private String name;
}
# 저장 SQL
INSERT INTO tutor(id, company_id, name) VALUES ...
INSERT INTO company(id, name) VALUES ...
# 조회 SQL
SELECT * FROM tutor where ...
SELECT * FROM company where ...

객체 중심 설계
User 객체 안에 Team객체를 필드로 가진다.(user.getTeam())// 객체 중심 설계
public class Tutor {
private Long id; // PK
private Company company; // 참조 연관관계
private String name;
public Company getCompany() {
return company;
}
public Company setCompany(Company company) {
this.company = company;
}
}
public Company {
private Long id; // PK
private String name;
}
// 조회 코드
public Tutor findById(Long tutorId) {
Tutor tutor = tutorRepository.findById(tutorId);
Company company = CompanyRepository.findByTutorId(tutorId);
tutor.setCompany(company);
return tutor;
}
조회 SQL
# 조회
# 조회 JOIN SQL 사용
SELECT t.*, c.* FROM tutor t JOIN company c ON t.company_id = c.id;
나중에 정리
예를 들어보면
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(100),
age INT
);
public class User {
private Long id;
private String name;
private int age;
}
ORM은 이 둘의 사이를 연결한다.
ORM과 SQL의 CRUD 작업 비교
SQL 중심
저장(Create)
INSETE INTO user ... 쿼리 작성
조회(Read)
SELECT * FROM user WHERE id=1;
수정(Update)
UPDATE user SET name = ...
삭제(Delete)
DELETE FROM user WHERE ...
ORM(객체) 중심
저장(Create)
repository.save(user); // 객체 저장
조회(Read)
repository.findById(1L); // ID로 객체 찾기
수정(Update)
user.update("새이름"); // 객체 값 변경
삭제(Delete)
repository.delete(user); // 객체 삭제
=> 정리하자면
테이블은 데이터 저장 구조이고, 객체는 애플리케이션이 다루는 구조
ORM은 객체와 관계형 DB를 자동으로 매핑(연결)하는 계층이다.
이 부분에 ORM 따로 정리한 부분 링크 첨부하기
블라블라 나중에 추가

EntityManager.persist(entity);

Tutor tutor = new Tutor();// 객체를 생성하고 영속성 컨텍스트에 저장하지 않은 상태
Tutor tutor = new Tutor(1L, "wonuk", 100);
commti시점에 DB에 반영)em.persist(tutor); 또는 em.find()로 가져온 객체
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
System.out.println("persist 전");
// 영속
em.persist(tutor);
System.out.println("persist 후");
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}
em.persist()가 호출되며 영속 상태가 된다.
persist 후 출력 이후에 SQL이 실행된다.em.detach() : 특정 엔티티만 준영속 상태로 변경한다.em.clear() : 영속성 컨텍스트를 초기화한다.em.close() : 영속성 컨텍스트를 종료한다.em.detach(tutor);, en.clear();, em.close();em.remove(tutor); (객체를 삭제한 상태)!! 엔티티 상태는 영속 상태일 때만 JPA의 영속성 컨텍스트가 데이터를 관리한다.
다음으로는 JPA가 객체지향적 편리함과 성능 최적화를 동시에 잡을 수 있게 햊주는 핵심 기능을 알아보자!!
영속성 컨텍스트 내부에는 엔티티를 보관하는 "1차 캐시"가 존재한다.
엔티티를 영속성 컨텍스트에 저장할 때 생성되는 메모리 내 캐시이다.
동작 방식
영속성 컨텍스트의 1차 캐시에 저장된다.

// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
// 영속, 1차 캐시에 저장
em.persist(tutor);
영속된 엔티티 조회할 때 DB가 아닌 1차 캐시에 저장된 엔티티를 먼저 조회한다.
find()로 호출 시 캐시에 데이터가 있다면 DB를 조회 X 메모리에서 즉시 반환한다.
// 1차 캐시에서 조회
Tutor findTutor = em.find(Tutor.class, 1L);
1차 캐시 전체 코드 예시
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 비영속
Tutor tutor = new Tutor(1L, "wonuk", 100);
// 영속
System.out.println("persist 전");
em.persist(tutor);
System.out.println("persist 후");
Tutor findTutor = em.find(Tutor.class, 1L);
System.out.println("findTutor.getId() = " + findTutor.getId());
System.out.println("findTutor.getName() = " + findTutor.getName());
System.out.println("findTutor.getAge() = " + findTutor.getAge());
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
emf.close();
}

위에서는 DB에 저장되기 전에 조회였다면 DB에 저장된 후의 데이터 조회를 알아보자
캐시에 데이터가 없으면 그때 DB를 조회하고, 조회된 데이터를 1차 캐시를 저장한 뒤 반환한다.

1차 캐시는 동일한 트랜잭션 안에서만 사용이 가능하다.
요청이 들어오고 트랙잭션이 종료되면 영속성 컨텍스트는 삭제된다.
코드 : xml <property name="hibernate.hbm2ddl.auto" value="none"/> 설정
public static void main(String[] args) {
// EntityManagerFactory 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
// EntityManager 생성
EntityManager em = emf.createEntityManager();
// Transaction 생성
EntityTransaction transaction = em.getTransaction();
// 트랜잭션 시작
transaction.begin();
try {
// 데이터베이스에서 조회 후 1차 캐시에 저장
Tutor findTutor = em.find(Tutor.class, 1L);
// 1차 캐시에서 조회
Tutor findCacheTutor = em.find(Tutor.class, 1L);
// transaction이 commit되며 실제 SQL이 실행된다.
transaction.commit();
} catch (Exception e) {
// 실패 -> 롤백
e.printStackTrace();
transaction.rollback();
} finally {
// 엔티티 매니저 연결 종료
em.close();
}
}

em.find(Menber.class, "member1") 호출 시, 1차 캐시에 데이터가 없으므로 DB에서 조회하고 결과를 1차 캐시에 저장한 뒤 반환한다.public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
try {
// 1. 첫 번째 조회 (DB에서 조회 후 1차 캐시에 저장)
System.out.println("=== FIRST FIND START ===");
Member findMember1 = em.find(Member.class, "member1");
System.out.println("member1.name = " + findMember1.getName());
// 2. 두 번째 조회 (1차 캐시에서 즉시 반환)
System.out.println("=== SECOND FIND START ===");
Member findMember2 = em.find(Member.class, "member1");
System.out.println("member2.name = " + findMember2.getName());
// 3. 동일성 비교 (==)
System.out.println("result = " + (findMember1 == findMember2));
tx.commit(); // 트랜잭션 커밋
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
=== FIRST FIND START ===
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id='member1'
member1.name = 홍길동
=== SECOND FIND START ===
member2.name = 홍길동
// 비교 결과 로그
result = true
new로 생성되어 false가 나왔겠지만, JPA 영속성 컨텍스트 덕분에 메모리 주소가 같은 동일 인스턴스임이 증명된다.
tutor1 1차 캐시에 저장tutor1 INSERT SQL 저장tutor2 저장

tutor2 1차 캐시에 저장tutor2 INSERT SQL 저장
flush되면서 SQL 쿼리 실행Commit되면서 실제 DB에 반영텍스트
정리
참고 자료
aws ORM이란 무엇인가요
스파르타 Spring 4기 강의 자료