diff --git "a/\354\225\210\354\236\254\353\257\274/2.4_JPA.md" "b/\354\225\210\354\236\254\353\257\274/2.4_JPA.md" new file mode 100644 index 0000000..d30ab5c --- /dev/null +++ "b/\354\225\210\354\236\254\353\257\274/2.4_JPA.md" @@ -0,0 +1,248 @@ +## 2.4 JPA + +### 기존 문제점 + +근본적으로 RDB와 자바 오브젝트의 성격이 다르기 때문에 발생하는 많은 불일치를 코드로 일일이 다뤄줘야함 + +- 텍스트 문장인 SQL을 직접 작성하고 파라미터를 바인딩하는 작업이 수고스러움 +- RDB 테이블에 자바오브젝트를 담기 위해서는 번거로운 작업이 필요함 + +### ORM (Object-Relational Mapping) + +> 오브젝트를 RDB에 저장하기 적절한 형태로 변환해주거나, RDB에 저장되어있는 데이터를 오브젝트로 변환해주는 기술 + +이러한 ORM 기술을 이용하여 자바 애플리케이션에서 데이터베이스와 상호작용하기 위한 자바 표준 스펙이 `JPA` 이다. +
+ +### 4.1 EntityManagerFactory 등록 + +`EntityManager` : JPA 영속성 컨텍스트를 관리하고, 데이터베이스와 상호작용을 담당하는 인터페이스 +이러한 `EntityManager`를 사용하기 위해서는 `EntityManagerFactory` 타입의 빈 등록이 필요하다. + +1. `LocalEntityManagerFactoryBean` + +- 프로바이더를 찾고 `META-INF/persistence.xml` 에 담긴 퍼시스턴스 유닛의 정보를 활용하여 `EntityManagerFactory` 를 생성 +- `DataSource` 빈을 사용할 수 없고, 바이트코드 위빙 기법도 적용할 수 없음 +- 이런 방법이 가능하다는 정도만 기억하고, 사용할 일이 드뭄 + +2. `LocalContainerEntityManagerFacotryBean` + +- 스프링이 직접 제공하는 컨테이너 관리하는 `EntityManageFactory`를 생성해준다. +- 이를 활용하면 JavaEE 서버에 배치하지 않아도 컨테이너에서 동작하는 JPA의 기능을 활용할 수 있다. + +```xml + + + + +``` + +스프링에 빈으로 등록된 `Datasource` 를 JPA 에서 사용할 수 있음 + +```xml + + + + springbook.learn.spring.jpa.Member + + + + + +``` + +```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 + + + + +``` + +다음과 같이 트랜잭션 매니저를 등록하고 `@Transactional` 이나 트랜잭션 AOP를 이용하여 자동으로 +JPA 트랜잭션을 시작하고 커밋하도록 만들 수 있다. + +### 4.2 EntityManager 와 JpaTemplate + +1. JpaTemplate + +- 템플릿 방식으로 JPA 코드를 작성할 수 있게 해준다. + +```java +List ms=templateDao.jpaTemplate.execute(new JpaCallback>(){ +public List 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를 시용하는 코드로만 만들어졌다. +그러면서도 스프링의 트랜잭션 통기화 기능과 완벽하게 연동돼서 동작하는 코드다. \ No newline at end of file