JPA에 대한 소개, 활용방안, Spring 프레임워크와 통합

Table of Contents

  1. JPA 란?
  2. JPA에서 주목할 부분
  3. 간단한 예제를 통한 JPA 소개
  4. EntityManager API
  5. Entity Lifecycle 과 Persistence Context
  6. META-INF/persistence.xml
  7. JAVA SE 에서 JPA 사용하기
  8. Spring 프레임워크와 통함
  9. 참고문헌

JPA 란?

JPA(Java Persistence API)는 EJB 2.x에서 DB에 접근하기 위해 사용되었던 Entity Bean을 
JSR-220(Enterprise JavaBeans 3.0)에서 대체하는 새로운 기술이다. 
JPA는 Entity Bean과는 아주 다른 POJO(Plain Old Java Object) 기반의 ORM(Object-Relational Mapping) 
프로그래밍 모델을 제공하며 기존에 존재하던 Hibernate와 같은 ORM솔루션과 유사하다. 
또한 EJB3.0에 국한되지 않은 범용적인 기술로 만들어 졌기 때문에 JAVA EE 와 SE 환경에서 
모두 사용 할 수 있으며 JAVA SE 5.0 Annotation을 사용하여 Java 객체에서 RDB로 Mapping하는 방법을 
단순화시켰다.

JPA에서 주목할 부분

  • POJO 기반의 단순한 Persistence Model
  • 표준화된 O/R 매핑
    • Annotation AND/OR XML 사용
    • 디폴트 규칙 적용으로 대부분의 경우 별도의 O/R 매핑을 지정할 필요가 없음.
  • 객체간의 상속관계 지원
  • EJBQL에 비해 확장된 쿼리 언어
    • Bulk Update/Delete, Subquery, Native Query 지원
  • Java EE And Java SE 환경 모두 지원
  • 프로바이더(Provider)를 플러그인 해서 사용 가능

간단한 예제를 통한 JPA 소개

  • 예제에 사용될 데이터 모델

    그림1> OrderApp 데이터 모델
  • DB의 데이터 모델은 JPA에서 Entity로 표현된다.
  • Entity는 데이터 모델을 객체 모델로 표현한 것으로 EJB 2.x의 Entity Bean과 유사하지만
    JPA에서는 별도의 인터 페이스 없이 POJO class로 표현된다.
  • class에서는 Entity임을 표시하기 위해 @Entity Annotation이 달려 있다.
    소스1> Customer.java — Customer 테이블과 Mapping
    /**
     * Customer entity.
     */
    
    @Entity  //---이 클레스가 Entity임을 표시 ---//
    @Table(name="ORDERAPP_CUSTOMER")
    @NamedQuery(name="findCustomerByName", query="select c from Customer c where c.name=:name")
    public class Customer implements Serializable {
    
        // ---Annotation이 필더에 달려 있을 경우 Field access type이라 한다.--- //
        @Id
        private String id;
        private String name;
        private String address;
    
        // ---cascade가 아래와 같이 설정되면 Customer entity가 생성/삭제 될때 --- //
        // ---해당되는 entity도 같이 생성/삭제된다.                           --- //
        @OneToMany(mappedBy="customer", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
        private List<Orders> orders;
        
        // ---Entity에서 Defult constructor 는 필수이다--- //
        public Customer(){}
        
        public Customer(String id, String name, String address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    
        public String getId() {return id;}
    
        public String getName(){return name;}
        
        public String getAddress(){return address;}
        
        public void setAddress(String address){this.address = address;}
        
        public List<Orders> getOrders(){return orders;}
        
        public void setOrders(List<Orders> orders) {this.orders = orders;}
    
        public void addOrder(Orders o){
            if(orders == null)
                orders = new LinkedList<Orders>();
            orders.add(o);
        }
    }
    
  • Entity는 기본적으로 하나의 Entity에 하나의 DB 테이블에 매핑된다.
    (M:N 관계가 존재시는 테이블이 하나더 존재한다)
    소스2> Item.java — Item 테이블과 Mapping
    /**
     * Item entity.
     */
    @Entity
    @Table(name="ORDERAPP_ITEM")
    @NamedQuery(name="findAllItems", query="select i from Item i")
    public class Item implements Serializable {
    
        private long id;
        private String name;
        private double price;
        
        public Item() {}
        
        public Item(String name, double price){
            this.name = name;
            this.price = price;
        }
    
        // ---Annotation이 프로퍼티에 달려 있을 경우 Property access type이라 한다.--- //
        @Id @GeneratedValue
        public long getId() {return id;}
    
        public void setId(long id) {this.id = id;}
        
        public String getName(){return name;}
        
        public void setName(String name){this.name = name;}
    
        public double getPrice() {return price;}
    
        public void setPrice(double price) {this.price = price;}
    }
    
    소스3> Order.java — Order 테이블과 Mapping
    /**
     * Order entity.
     */
    @Entity
    @Table(name="ORDERAPP_ORDERS")
    public class Orders implements Serializable {
        
        @Id @GeneratedValue
        private Long id;
        @ManyToOne
        private Customer customer;
        @ManyToMany
        @JoinTable(name="ORDERAPP_ORDERS2ITEMS")
        private Collection<Item> items;
        
        public Orders() {}
        
        public Long getId() {return id;}
        
        public void setId(Long id) {this.id = id;}
        
        public Customer getCustomer(){return customer;}
        
        public void setCustomer(Customer c){this.customer = c;}
        
        public Collection<Item> getItems(){return items;}
        
        public void setItems(Collection<Item> items){this.items = items;}
    }
    
  • 예제 소스에 사용된 O/R 매핑 표준 Annotation
    • Logical annotation
      • @id - Primary Key에 해당하는 ID필드임을 표시한다.
      • @GeneratedValue - DB에 의해서 값이 자동으로 생성되는 필드임을 표시한다.
    • Physical annotation
      • @Table - Table mapping
  • 예제 소스에 사용된 Relationships 표준 Annotation
    • @OneToMany - 1:N 관계를 표현하기 위한 필드임을 표시한다.
    • @ManyToOne - N:1 관계를 표현하기 위한 필드임을 표시한다.
    • @ManyTOMany - M:N 관계를 표현하기 위한 필드임을 표시한다.
    • @JoinTable - 관계를 표현할 때 어떤 테이블을 사용할 것인지에 대해 표시한다.
  • 예제 소스에 사용된 Query 표준 Annotation
    • @NamedQuery - Static query 를 entity class에 직접 명기
  • Cascaed Type (연쇄작업 설정)
    • CascadeType.PESIST - 개체 삽입
    • CascadeType.MERGE - 업데이트
    • CascadeType.REMOVE - 삭제
    • CascadeType.REFRESH - 리플레쉬
    • CascadeType.ALL - 전부 적용

EntityManager API

  • DB에서 엔티티를 가져오거나 생성, 삭제 하는 일은 모두 EntityManager API를 통해서 이루어진다.
    EntityManager는 누가 관리되는가에 따라 크게 두가지로 구분된다.
    • Container-managed EntityManager

      JAVA EE 환경에서만 사용되며 컨테이너가 EntityManager 인스턴스의 Lifecycle을 관리한다.
      JTA 트랜잭션에 자동으로 연결되기 때문에 JAVA EE 환경에 유용하다.

      • Container-managerd 에서 EntityManager 객체를 가져오는 방법
        Container-managerd EntityManager의 경우 인젝션이나 lookup을 통해서 얻어올 수 있다.
        1. 인젝션 이용. 해당 필드나 메소드에 @PersistenceContext표기
          @PersistenceContext
          private EntityManager em;
          
        2. lookup 이용. Class에 @PersistenceContext를 표기해 EntityManager를
          ENC(Environment Naming Context) 네임스페이스에 등록
          @PersistenceContext(name="em", unitName="HR")
          public class HelloBean implements Hello {
          // ...
          InitialContextic = new InitialContext();
          EntityManager em = (EntityManager)ic.lookup("java:comp/env/em");
          
    • Application-managerd EntityManager

      JAVA EE, JAVA SE 두 환경 모두 사용되며 개발자가 EntityManager 인스턴스의 Lifecycle을 직접 관리하며
      트랜젝션도 직접 관리해야 한다.

      • Application-managerd 에서 EntityManager 객체를 가져오는 방법
        Application-managerd EntityManager의 경우 EntityManagerFactory API를 통해서 얻어올 수 있다.
        @PersistenceContext
        private EntityManagerFactory emf;
        // ...
        EntityManager em = emf.createEntityManager();
        
  • Entity 객체를 만드는 것은 일반 JAVA 객체를 생성하듯이 new 생성자를 사용하면 되며 이를
    DB에 저장하려면 다음과 같이 Entitymanager.persist() 메소드를 호출해야 한다.
    Customer customer = new Customer(id, name, address);
    em.persist(customer);
    
  • DB에서 하나의 Entity 객체를 가져오려면 EntityManager.find() 메소드를 호출한다.
    이경우에는 Primary Key에 해당하는 값을 통해서 Entity를 가져올 수 있다.
    Customer e = em.find(Customer.class, id);
    
  • Java Persistence Query Language를 이용하여 모든 Entity들을 가져오거나 특정 필드값을 비교하여
    Entity를 가져올 수 있다.
    Query query = em.createQuery("select c from Customer c");
    List list = query.getResultList();
    
  • Entity 필드를 수정하려면 Entity 객체의 필드를 수정하면 된다.
    해당 필드는 EntityManager.flush() 를 호출하거나 트렌잭션이 commit 될때 DB와 동기화 된다.
    Customer e = em.find(Customer.class, id);
    c.setAddress(address);
    
  • Entity를 삭제할 때에는 EntityManager.remove() 를 호출한다.
    em.remove(customer);
    
  • Entity간의 관계(Relationship)는 다른 Entity에 대한 레퍼런스로 표현된다.
    관계를 수정할려면 레퍼런스 값을 바꾸면 된다.
    ManyTOMany나 OneToMany 같이 여러 개의 Entity와 관계를 맺고 있을때는 Collection객체로 표현된 
    값을 바꾼다.
    public class Customer //...
    
    @OneToMany(mappedBy="customer", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
    private List<Orders> orders;
    
    public void addOrder(Orders o) {
        orders.add(o);
    }
    

Entity Lifecycle 과 Persistence Context

  • Persistence Context ?
    현재 관리되고 있는 Entity 인스턴스들의 집합을 말함.
    Entity 인스턴스의 상태는 EntityManager 오퍼레이션과 밀접한 관련이 있다.
  • EntityManager 오퍼레이션에 따른 Entity의 Lifecycle

    그림2> EntityManager 오퍼레이션에 따른 Entity의 Lifecycle
    • NEW - Entity instance가 생성되었을 때의 상태로 DB와 연결되어 있지 않다.
      EntityManager.perisit()를 통해서 DB에 저장되며 Managed 상태가 된다.
    • Managed - Entity instance가 Persistence Context에 의해서 관리되는 상태로 Entity 인스턴스
      값의 변화가 DB에 반영된다.
      Container-managed EntityManager의 경우 트랜잭션이 종료되면 모든 Entity는 detached 상태가 된다.
    • Detached - Entity instance가 Persistence Context에 의해서 더이상 관리되지 않는 상태로 DB와 연결되지 않는다.
      EntityManger.merge()를 통해서 다시 managed 상태로 만들 수 있다.
    • Removed - DB에서 해당 Entity가 삭제될 상태이다.

Java Persistence Query Language

EJBQL과 비슷하지만 JPQL은 Bluk Update/Delete, Subquery, Native Query(DB-specific SQL) 들을 지원한다.

  • Query API
    Query Object는 EntityManager의 createQuery()를 이용해 생성한다.
    • setParameter(), setMaxResults(), setFirstResult()
    • getResultList(), getSingleResult(), executeUpdate()
  • Dynamic Query
    동적으로 생성하여 사용하는 query
    Query query = em.createQuery("select c from Customer c where name=:name");
    query.setParameter("name", name);
    List list = query.getResultList();;
    
  • Static Query
    Entity Class에 선언해 두고 필요할때 마다 불러 쓰는 query
    @NamedQuery 와 @NamedNativeQuery 가 있다.
    @Entity
    @NamedQuery(name="findCustomerByName", query="select c form Customer c where c.name=:name")
    public class Customer { //.... }
    
    Query query = em.createNamedQuery("findCustomerByName");
    query.setParameter("name", name);
    List list = query.getResultList();
    
  • An extension of EJBQL
    • Projection list
    • Explicit JOINS
    • Subqueries
    • GROUP BY, HAVING
    • EXISTS, ALL, SOME/ANY
    • Bulk UPDATE, DELETE

META-INF/persistence.xml

  • Entity Class를 패키징 할때 META-INF/persistence.xml 디스크립터가 꼭 필요하다.
  • persistence.xml은 관련 Entity Class들과 이에 대한 DB DataSource 설정 등과 같이
    Persistence-Unit에 대한 정보를 담고 있다.
  • Persistence-Unit은 Entity를 묶는 단위이며 이것은 하나의 DB에만 매핑된다.
  • Persistence-Unit의 UnitName은 EntityManager or EntityManagerFactory를 얻을때 사용된다.
  • persistence.xml 예제
     
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
      <persistence-unit name="orderunit">
        <jta-data-source>jdbc/sample</jta-data-source>
        <properties>
          <!-- vendor-specific -->
          <!--
            <property name="toplink.ddl-generation" value="create-tables"/>
          -->
        </properties>
      </persistence-unit>
    </persistence>
    

JAVA SE 에서 JPA 사용하기

  • JAVA SE 환경에서 사용 할 때는 Application-managed EntityManager 만 사용할 수 있다.
  • JDBC 드라이버에 국한된 로컬(resource-local) 트랜잭션만 쓸 수 있다.
    • EntityTransaction API를 이용하여 직접 트랜잭션 컨트롤을 해주어야 함.
  • EntityManagerFactory를 얻는 방법 - javax.persistence.spi.Persistence Bootstrap API 사용
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("orderunit");
    EntityManager em = emf.createEntityManager();
    //...
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.persist(customer);
    tx.commit();
    //...
    em.close();
    emf.close();
    
  • META-INF/persistence.xml
    JAVA EE 환경과는 달리 모든 Entity 클래스를 명시적으로 나열해 주어야 한다.
    //...
      <persistence-unit name="orderunit">
        <class>orderapp.entities.Customer</class>
        <class>orderapp.entities.Item</class>
        <class>orderapp.entities.Order</class>
    //...
    

Spring 프레임워크와 통함

아래 문서에서 정보를 얻으시기 바랍니다
Spring과 Java Persistence API의 사용
The Spring Framework - Reference documentation

참고문헌


출처 - http://www.javajigi.net/pages/viewpage.action?pageId=5924



Posted by linuxism
,