Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions 안재민/2.4_JPA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
## 2.4 JPA

### 기존 문제점

근본적으로 RDB와 자바 오브젝트의 성격이 다르기 때문에 발생하는 많은 불일치를 코드로 일일이 다뤄줘야함

- 텍스트 문장인 SQL을 직접 작성하고 파라미터를 바인딩하는 작업이 수고스러움
- RDB 테이블에 자바오브젝트를 담기 위해서는 번거로운 작업이 필요함

### ORM (Object-Relational Mapping)

> 오브젝트를 RDB에 저장하기 적절한 형태로 변환해주거나, RDB에 저장되어있는 데이터를 오브젝트로 변환해주는 기술

이러한 ORM 기술을 이용하여 자바 애플리케이션에서 데이터베이스와 상호작용하기 위한 자바 표준 스펙이 `JPA` 이다.
<br>

### 4.1 EntityManagerFactory 등록

`EntityManager` : JPA 영속성 컨텍스트를 관리하고, 데이터베이스와 상호작용을 담당하는 인터페이스
이러한 `EntityManager`를 사용하기 위해서는 `EntityManagerFactory` 타입의 빈 등록이 필요하다.

1. `LocalEntityManagerFactoryBean`

- 프로바이더를 찾고 `META-INF/persistence.xml` 에 담긴 퍼시스턴스 유닛의 정보를 활용하여 `EntityManagerFactory` 를 생성
- `DataSource` 빈을 사용할 수 없고, 바이트코드 위빙 기법도 적용할 수 없음
- 이런 방법이 가능하다는 정도만 기억하고, 사용할 일이 드뭄

2. `LocalContainerEntityManagerFacotryBean`

- 스프링이 직접 제공하는 컨테이너 관리하는 `EntityManageFactory`를 생성해준다.
- 이를 활용하면 JavaEE 서버에 배치하지 않아도 컨테이너에서 동작하는 JPA의 기능을 활용할 수 있다.

```xml

<bean id="emf"
class="org.springframework.orm.jpa .LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
```

스프링에 빈으로 등록된 `Datasource` 를 JPA 에서 사용할 수 있음

```xml
<?xml version="1.0“ encoding=“ UTF-8"?>
<persistence xmlns="http://java.sun.com/xml!ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml!ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="default">
<class>springbook.learn.spring.jpa.Member</class>
<properties>
<property name="eclipselink.weaving" value="false"/>
</properties>
</persistence-unit>
</persistence>
```

```java
import jakarta.persistence.Column;
import jakarta.persistence.Id;

@Entity
public class Member {
@Id
int id;

@Column(length = 100)
String name;

@Column(nullable = false)
double point;
}
```

**`loadTimeWeaver`**
JPA는 그래서 단순한 자바 코드로 만들어진 엔티티 클래스의 바이트코드를 직접 조작해서 확장된 기능을 추가하는 방식을 이용한다.
이를 통해 엔티티 오브젝트 사 이에 지연된로딩이 가능하고, 엔티티 값의 변화를추적할수있으며,최적화와 그룹 페칭 등의 고급 기능을 적용할 수 있다.
이렇게 이미 컴파일된 클래스 바이트 코드를 조작해서 새로운 기능을 추가하는 것을 바이트코드 항상 기법이라고 한다.

1. 바이트 코드를 빌드중에 변경하는 방법

- 바이트코드 컴파일러를 통해 매번 빌드 과정에 바이트 코드를 조작한다.

2. 런타임 시에 바이트 코드를 메모리에 로딩하여 다이나믹하게 변경하는 방법

- 런타 임 시에 클래스를 로딩하면서 기능을 추가하는 것을 로드타임 위빙(loadtimeweaving) 이라 하고,이런 기능을 가진 클래스를 로드타임 위버 (loadtimeweaver) 라고 부른다.

**`트랜잭션 매니저`**
스프링 JDBC는 자동 트랜잭션 모드를 갖고 있기 때문에 명시적으로 트랜잭션 관리를 해주지 않아도 된다.
반면에 JPA는 반드시 트랜잭션 안에서 동작하도록 설계되어 있다.

```xml

<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="emf"/>
</bean>
```

다음과 같이 트랜잭션 매니저를 등록하고 `@Transactional` 이나 트랜잭션 AOP를 이용하여 자동으로
JPA 트랜잭션을 시작하고 커밋하도록 만들 수 있다.

### 4.2 EntityManager 와 JpaTemplate

1. JpaTemplate

- 템플릿 방식으로 JPA 코드를 작성할 수 있게 해준다.

```java
List<Member> ms=templateDao.jpaTemplate.execute(new JpaCallback<List<Member>>(){
public List<Member> doInJpa(EntityManager em)throws PersistenceException{
return em.createQuery("select m from Member m").getResultList();
}
});
```

`JpaTemplate`은 콜백 오브젝트 없이도 간단한 메소드를 이용해서 EntityManager 가 제공하는 대부분의 기능을 사용하게 해준다.
다음은 `EntityManager`의 `persist()` 와 `find()` 에 각각 대응되는 JpaTemplate의 `persist()`와 `find()` 메소드를 이용해서 Member 오브젝트를 저장한 뒤에
이를 조회히는 코드다.

```java
Member m=new Member(1,"Spring",8.9);
jpaTemplate.persist(m);
Member m2=templateDao.jpaTemplate.find(Member.class,1);
```

2. 애플리케이션 관리 EntityManager 와 @PersistenceUnit
애플리케이션 코드가 관리하는 EntityManager 를 이용하는 것이기 때문에 JavaEE, JavaSE 모두 가능하다.

```java
em.getTransaction().begin();
Member m=new Member(1,"Spring",7.8);
em.persist(m);
Long count=em.createQuery("select count(m) from Member m",
Long.class).getSingleResult();
em.getTransaction().commit();
```

이렇게 EntityManager를 DAO 클래스에 DI 하는 방법은 두 가지가 있다.

1. @Autowired, @Resource
EntityManagerFactory 타입의 빈을 일반적인 스프링 DI 방식으로 DAO로 가져와 사용한다.
2. @PersistenceUnit
`@PersistenceUnit` 어노테이션을 이용하여 DI 받는데, 이는 스프링 프레임워크에 대한 의존도가 전혀 없는 코드이며, 따라서
스프링이 아닌 JPA 환경에서도 사용할 수 있다는 장점이 있다.


3. 컨테이너 관리 EntityManager 와 @PersistenceContext

```java
public class MemberDao {
@PersistenceContext
EntityManager em;

public void addMember(Member member) {
em.persist(member);
}
}
```

잘 보면 factory가 아닌 EntityManager 자체를 주입받게 된다. em은 빈으로 등록되지 않는 객체인데 어떻게 가능할까?
여기서 주입받는 EntityManager는 실제 EntityManager 가 아닌 일종의 프록시로 현재 진행중인 트랜잭션에 연결된다.
이러한 방식은 스프링이 제공해주는 템플릿/콜백 방식을 시용하지 않아도 된다는 장점이 있다. 코드는 간결해지지만 스프링의 트랜잭션 경계설정과 동기화 기법을 적용한 것 과 같은 혜택은 그대로 얻을 수 있다.

4. @PersistenceContext 와 확장된 퍼시스턴스 컨텍스트
3번 방식과 동일하지만, 주입받는 EntityManager의 스코프가 다르다.
type이 PersistenceContextType.EXTENDED로 설정되어 트랜잭션 스코프 대신 확장된 스코프를 갖는 em을 주입받아
상태유지 세션빈에 사용할 수 있는 EntityManager를 생성해준다.

**JPA 예외 변환 AOP**
JPA API를 이용하는 경우에도 JPA 예외를 스프링의 DataAccessException 예외로 전환시킬 수 있다.
이를 위해서는 스프링의 AOP를 이용하면 된다.

- @Repository
해당 애노테이션이 붙은 클래스의 메소드는 예외 변한 기능이 부가될 빈으로 선정된다.
- PersistenceExceptionTranslationPostProcessor
예외 변환 기능을 가진 AOP 어드바이스를 적용해주는 후처리기가 필요하다.
`PersistenceExceptionTranslationPostProcessor`를 빈으로 등록해주기만 하면
JPA 예외가 스프링의 DataAccessException 으로 전환되어 서비스 계층으로 던져지게 된다.
그러나 DataAccessException 의 다양한 서브클래스로 매핑돼서 던져지지는 않는다.

## 2.5 하이버네이트 (Hibernate)

### 5.1 SessionFactory 등록

하이버네이트에는 JPA의 EntityManagerFactory 처럼 핵심 엔진 역할을 하는 SessionFactory가 있다.
스프링에서는 이를 생성하는 두가지 팩토리 빈을 제공한다.

**`LocalSessionFactoryBean`**
빈으로 등록된 DataSource를 이용하여 스프링이 제공하는 트랜잭션 매니저와 연동할 수 있도록 설정된 `SessionFactory`를 만들어주는 빈이다.

**`AnnotationSessionFactoryBean`**
하이버네이트는 JPA처럼 엔티티 클래스에 애노테이션을 이용하는 방법을 제공한다.
스프링에서는 XML 매핑파일 대신 애노테이션 매핑정보를 이용해서 SessionFactory를 생성해주는 AnnotationSessionFactoryBean을 제공한다.

**`트랜잭션 매니저`**

1. HibernateTransactionManager
단일 DB를 사용하고 JTA를 이용할 필요가 없다면 간단히 HibernateTransactionManager 빈을 추가해주면 된다.
2. JtaTransactionManager
여러 개의 DB에 대한 작업을 하나의 트랜잭션으로 묶으려면 JTA를 통해서 서버가 제공하는 글로벌 트랜잭션 기능을 이용해야 한다.

### 5.2 Session 과 HibernateTemplate

Session은 하이벼네이트의 핵심 API다. Session은 SessionFactory로부터 만들어지며 보통 트랜잭션과 동일한 스코프를 갖고 있다.
하이버네이트 DAO는 스프링이 관리하는 트랜잭션과 동기화된 Session을 가져와 사용한다. 스프링은 Session을 사용하는 두 가지 방법을 제공한다.
**`HibernateTemplate`**
HibernateTemplate을 이용하면 복잡한 설정 없이도 간단히 DAO를 테스트할 수 있다.
```java
public class MemberDao {
private HibernateTemplate hibernateTemplate;

@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
hibernateTemplate = new HibernateTemplate(sessionFactory);
}

public void addMember(Member member) {
hibernateTemplate.save(member);
}
}
```

```java
public class MemberDao extends HibernateDaoSupport {
public void addMember(Member member) {
getHibernateTemplate().save(member);
}
}
```


**`SessionFactory.getCurrentSession()`**
하이버네이트 SessionFactory의 getCurrentSession() 메소드는 현재 트랜잭션에 연결되어 있는 하이버네이트 Session을 돌려준다.
이를 이용하면 스프링의 트랜잭션 매니저 또는 JTA의 트랜잭션에 연동되어 만들어지는 Session을 가져올 수 있다.
```java
public class MemberDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void addMember(Member member) {
sessionFactory.getCurrentSession().save(member);
}
}
```
MemberDao에는 스프링 API가 전혀 등장하지 않는다. 펑범한 하이버네이트 API를 시용하는 코드로만 만들어졌다.
그러면서도 스프링의 트랜잭션 통기화 기능과 완벽하게 연동돼서 동작하는 코드다.