JPA & Entity

JPA란?

JPA = Java Persistence API 는 Java object를 relational database에 mapping하기위해 사용되는 Java standard(표준)이다.

JPA는 ORM(Object Relational Mapping) framework에 대한 표준화 작업을 위해 만들어진것인데, JPA를 통해 개발자가 relational database로 부터 data를 map, store, update, retreive할 수 있고 database의 data를 Java object와 연동시킬 수 있다.

잠깐 ORM이 무엇인지 다시 리뷰해보자.

ORM이란?

Java object와 database의 table을 mapping할때에 ORM을 사용한다. ORM은 Java object에 저장된 데이터를 table의 row 정보로 저장하고, 또 반대로 테이블의 row 정보를 java object로 mapping해준다.

(The Object Relational Mapping is the base of JPA, which is all about representing and accessing data in the form of plain Java Objects – called Entities.) 여기서 mapping되는 java object를 entity라고 명칭한다.

이 과정에서 사용되는 SQL 구문과 java 코드는 ORM framework가 자동으로 만들어준다. 이것은 ORM framework를 사용하는 이유중 하나이다. DB 연동에 필요한 SQL을 자동으로 생성하고, DBMS가 변경될때 자동으로 SQL문도 변경된다.

Java언어로 사용하는 ORM framework의 대표적인 종류로는 Hibernate, TopLink(by Oracle), Apache Cayenne, Apache OpenJPA, Ebean 등이 있다.
(참고: 앞으로 사용할 Python의 대표적인 ORM framework로는 Django, SQLAlchemy, SQLObject 등이 있음)

객체관계 mapping, 객체와 relational Database를 별개로 설계하고 ORM은 중간에서 mapping해주는 역할을 하는것이다.

Relational Database에 data 그 자체와 mapping하기때문에 SQL을 직접 작성할 필요가 없다. (unlike Java Persistence framework중 하나인 MyBatis. MyBatis는 SQL문을 직접 설정파일에 구현해야한다)

JPA의 역할은 모든 ORM framework(구현체)들의 공통적인 interface를 제공하는 것이다.

JPA의 구조

JPA를 이해하기위해 JDBC API와 비교해볼수있다.

JDBC API는 특정 DBMS에 종속되지 않는 DB연동이 구현되도록 지원하는데, 이는 JDBC API(java.sql)의 interfaces만을 이용해서 실질적인 DB연동 처리는 해당 DBMS의 driver class들이 처리하기때문에 가능한것이다.

DBMS가 변경되는 상황에서도 interface를 실제 구현하는 driver class만 변경하면 JDBC API롤 이용하는 application은 수정없이 DBMS를 쉽게 변경할 수 있다.

이렇게 application이 JDBC를 활용하는 구조는 아래 그림으로 표현된다.

JPA도 이와 비슷한 구조로 구성된다고 생각하면된다. JPA도 JDBC와 마찬가지로 application을 구현할때, JPA API(javax.persistence)를 이용해서 실제 DB 연동처리는 각 DBMS의 driver가 처리하도록 한다.


Hibernate 실습

실습에서는 Hibernate을 사용하여 ORM framework기반의 JPA project를 구현해보았다.

STS에서 Maven을 사용해서 project를 build했다.

File>New>Maven Project를 선택 후, 아래와 같이 설정한다.

Group Id: org.apache.maven.archetype
Artifact Id: maven-archetype-quickstart

생성한 project에서 JPA를 사용하기위해서는 Project의 properties에가서 Project Facets의 configuration을 설정해준다. JPA(vr. 2.1 or 2.2)와 Java(vr. 1.8)이 함께 체크되어있어야 한다. (만약 project facets에 JPA가 없다면, STS>Help>New Install Software에가서 JPA관련 software를 다운받으면 된다.
Laste Eclipse Release - https://download.eclipse.org/releases/latest 선택 -> Web, XML, Java EE and OSGI Enterprise Development 항목을 펼쳐서 JPA관련 모든 항목을 체크한다.)

Maven으로 project를 build할때에 pom.xml에 project에 필요한 library를 명시해둔다.

JPA Hibernate을 사용하기위해서는 pom.xml에 아래와 같은 dependency를 선언해야한다.

<dependencies>
....
<!-- JPA, 하이버네이트 -->
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-entitymanager</artifactId>
   <version>5.1.0.Final</version>
  </dependency>
  
<!-- java.lang.NoClassDefFoundError: 
  javax/xml/bind/JAXBException 오류시 -->
  <dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
  </dependency>
  ....
</dependencies>

JPA의 entity로 사용 할 class를 생성한다. 이 클래스는 데이터베이스의 테이블과 매핑될 영속 클래스이다. 데이터베이스의 테이블과 매핑될 영속 클래스를 작성한다.

File>New>JPA Entity를 선택해서 vo역할을 할 java class를 하나 생성한다.

BBS.java:

package com.study.bbs;

import java.util.Date;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * Entity implementation class for Entity: BBS
 * Entity의 Attribute(Id)가 define되어야한다
 */
@Entity
public class BBS {

	/* Id annotation: Id가 표기된 변수 bbsno가 BBS 테이블의 primary key column이다.
    	 * 만든것을 가져다가 사용하는것이기때문에
    	 * SequenceGenrator의 name과 GeneratedValue의 generator가 같아야함!	
	 */
    	@Id
	@SequenceGenerator(name="BBSNO_SEQ", sequenceName="SEQ_BBS", allocationSize=1)
	@GeneratedValue(generator = "BBSNO_SEQ")
	private int bbsno;
    
	private String wname;
    
	private String title;
    
	private String content;
    
	private String passwd;
    
	private int viewcnt;
    
	@Temporal(TemporalType.DATE)
	private Date wdate = new Date();
    
	private int grpno;
    
	private int indent;
    
	private int ansnum;
    
	private String filename;
    
	private int filesize;
	
	//생성자
	public BBS() {
		super();
	}
    
    //setter, getter, toString(override)도 작성한다
}

src/main/java/META-INF/persistence.xml에 JPA에 필요한 정보를 입력한다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"	xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"	
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence 
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
	<persistence-unit name="JPAProject">
		<class>com.study.bbs.BBS</class>
		<properties>
			<!-- 필수 속성 -->
			<property name="hibernate.dialect"
				value="org.hibernate.dialect.Oracle12cDialect" />
			<property name="javax.persistence.jdbc.driver"
				value="oracle.jdbc.driver.OracleDriver" />
			<property name="javax.persistence.jdbc.user"
				value="user1234" />
			<property name="javax.persistence.jdbc.password"
				value="1234" />
			<property name="javax.persistence.jdbc.url"
				value="jdbc:oracle:thin:@localhost:1521:XE" />

			<!-- 옵션 (해도되고 안해도되는 부분)-->
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.format_sql" value="true" />
			<property name="hibernate.use_sql_comments" value="false" />
			<property name="hibernate.id.new_generator_mappings"
				value="true" />
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.hbm2ddl.auto" value="update" />
		</properties>
	</persistence-unit>
</persistence>

이제 BBS 테이블의 data를 access하는 service를 구현한다고 가정하고 BBSServiceClient.java에서 client program을 작성해보았다.

package com.study.bbs;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class BBSServiceClient {

	public static void main(String[] args) {
	//Persistence.createEntityManagerFactory("")의 매개변수로는
	//persistence.xml파일에서 정의한 <persistence-unit name="JPAProject"> name 그대로 사용 
	    EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAProject");
	    EntityManager em = emf.createEntityManager();
	    EntityTransaction tx = em.getTransaction();
	    
	    try {
	      //Transaction 시작
	      tx.begin();
	      
	      BBS bbs = new BBS();
	      bbs.setWname("라일락");
	      bbs.setTitle("jpa 제목");
	      bbs.setContent("jpa 내용");
	      bbs.setPasswd("1234");
	      
	      //등록
	      em.persist(bbs);
	      
	      //글목록 조회
	      String jpql = "select b from BBS b order by b.bbsno desc";
	      List<BBS> list = em.createQuery(jpql, BBS.class).getResultList();
	      for(BBS bs : list) {
	        System.out.println("--->"+bs.toString());
	      }
	       
	      tx.commit();
	      
	    }catch(Exception e) {
	      e.printStackTrace();
	      tx.rollback();
	    }finally {
	      em.close();
	    }
	    emf.close();
	}
}

EntityManagerFactory 타입의 객체를 생성해서 JPAProject를 등록하고, EntityManager 객체를 생성해서 이를 통해 getTransaction() 메소드를 호출해서 RDB와 java entity간의 transaction을 시작한다.

BBS entity 클래스의 object bbs를 새로 생성하고 변수값을 (via setter methods) 초기화한다.

em.persist(bbs)

를 실행하여 Oracle DB에 등록한다. 그리고 Oracle DB에 BBD테이블에 등록한 row 정보를 조회하기위해 아래와같은 JPQL을 작성해서 문자열 jpql에 assign한다.

String jpql = "select b from BBS b order by b.bbsno desc"

JPQL은 JPA에서 제공하는 명령어이다. Hibernate만을 위한 명령어는 HQL인데, JPA에서는 JPQL, HQL 둘다 사용을 할 수 있다. JPA를 사용하는것이 spring에서는 더 편하기때문에, 보통 JPA에서 JPQL을 더 많이 쓰고 부족한 부분만 HQL을 사용한다고 한다.

(JPQL은 여러 ORM framework에서 작동하는데에 비해 HQL은 Hibernate에서만 동작한다.)

List<BBS> list = em.createQuery(jpql, BBS.class).getResultList();
for(BBS bs : list) {
	System.out.println("--->"+bs.toString());
}

BBS 테이블의 row정보들을 BBS 클래스 객체로 list형태로 불러와서 콘솔창에 출력한다.

persistence.xml에서 JPA Hibernate을 설정할때에 Hibernate구동시 자동 생성되는 SQL문을 콘솔창에 출력하도록 옵션으로 설정했기때문에 아래와 같은 SQL문 또한 콘솔창에 출력된다.

Hibernate: 
    insert 
    into
        BBS
        (ansnum, content, filename, filesize, grpno, indent, passwd, title, viewcnt, wdate, wname, bbsno) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    select
        bbs0_.bbsno as bbsno1_0_,
        bbs0_.ansnum as ansnum2_0_,
        bbs0_.content as content3_0_,
        bbs0_.filename as filename4_0_,
        bbs0_.filesize as filesize5_0_,
        bbs0_.grpno as grpno6_0_,
        bbs0_.indent as indent7_0_,
        bbs0_.passwd as passwd8_0_,
        bbs0_.title as title9_0_,
        bbs0_.viewcnt as viewcnt10_0_,
        bbs0_.wdate as wdate11_0_,
        bbs0_.wname as wname12_0_ 
    from
        BBS bbs0_ 
    order by
        bbs0_.bbsno desc

MyBatis

ORM framework은 아니지만, 동일하게 Java object data의 persist역할을 수행하는데에 매우 많이 활용되는 persistence framework로는 MyBatis가 있다. Spring framework기반 application에서 DB를 연동할때에 Hibernate 또는 MyBatis가 자주 사용된다고한다.

아파치(Apache) 소프트웨어 재단의 IBatis 개발자팀이 만들어서 이전엔 IBatis였지만, 구글 코드로 이전하고, 구글 코드에서는 이름이 MyBatis로 변경되었다.

MyBatis는 개발자가 지정한 SQL, procedure, advanced mapping 등을 지원한다. JDBC로 data access를 처리하는 부분과 parameter 설정 및 결과 mapping을 대신 처리해주는 역할을 한다. XML 또는 annotation 기반으로 SQL mapper를 지원해준다. Hibernate와는 다르게 SQL문을 mapping을 설정하는 xml파일에 개발자가 작성해야해서 번거로운 반면, 복잡한 데이터 테이블간의 join, tuning등을 더 수월하게 진행할 수 있다.

Mybatis Framework가 Business Layer(Persistance Layer + Service Layer)와 DB Layer사이 가운데서 양쪽을 연결하고 있다.

MyBatis의 구성요소

(notes from lectureBlue site)

(1) Configuration 파일(SqlMapConfig.xml)

  • Mybatis 메인 환경설정를 정의 한다.
  • DB설정및 mapper 설정등을 한다.
  • DB 설정은 별도의 properties 파일로 분리할 수 있다.
  • mapper는 SQL query를 xml문서로 분리한 것이다.

(2) 매퍼(Mapper)

  • 두가지 종류의 매퍼를 정의 할 수 있다.
  • 첫째는 SQL을 XML에 정의된 XML파일로 생성
    (< mapper >루트 엘리먼트를 가지는 XML파일이다. mapper 엘리먼트는 namespace 속성을 가진다. namespace 속성에 mapping하려는 java 클래스를 입력한다.)
  • 두번째는 SQL을 메소드에 어노테이션으로 정의한 interface로 생성

(3) 매핑구문(Mapped Statements)

  • SQL을 DB에 실행할 구문을 의미한다.
    매핑 구문은 어노테이션 정의 방법과 XML정의 방식 두가지가 있다.

(4) Mybatis Java API

  • SqlSession 는 Mapper xml에 등록된 SQL구분을 실행한다.
  • SqlSession 객체는 SQL구분을 실행하기 위한 여러가지 메소드를
    제공한다. selectOne(), selectList(), insert(), update(), delete() 등

MyBatis Mapper XML 파일 구조

1. 명령 구문 공통적으로 mapper XML파일에 사용되는 주요 요소/속성:

id 속성
필수 속성으로 전체 Mapper 파일들 내에서 유일한 아이디를 등록한다. mapper xml파일에서의 id와 mapper interface에서의 method 이름이 match되어야한다.

< mapper namespace="">에서 namespace값이 다른면 같은 id도 다른 id로 인식된다. 각각의 테이블마다 반복적으로 create, read, update, delete 기능이 필요한데, namespace가 다르면 같은 id도 다른 id로 인식되는것이 유용하다.

parameterType 속성
외부로부터 데이터를 받아올때 사용한다. SQL select, insert, update, delete 명령과 연동된 java 메소드를 실행할때에 어떤 조건의 row 정보를 선택할 것인지 매개변수로 전달을 하는데, 이 매개변수의 데이터 타입이 parameterType속성에 반영된다.

CDATA Section
SQL 구문에 값 비교를 위해 <를 사용하면 에러가 난다. (xml에서 <는 태그의 시작으로 인식하기때문에) xml parser가 <를 단순 문자로 인식하게하도록 아래와 같이 처리한다.

<select id=”selectBoard” parameterType=”int” resultType=”board”>
    SELECT *
    FROM board
   <![CDATA[
    WHERE num <= #{num}
    ]]>
</select>

동적 SQL
(1) if문의 구현: if태그의 test속성을 사용한다.

<update id="update" parameterType="BbsDTO">
	UPDATE bbs
	<set>
		wname = #{wname},
		title = #{title},
		content = #{content},
		<if test="filesize > 0">
			filename = #{filename},
			filesize = #{filesize}
		</if>
	</set>
	WHERE bbsno = #{bbsno}
</update>

(2) if, else if, else문의 구현: choose, when, otherwise 태그를 사용한다

<select id=”findActiveBlogLike”
        parameterType=”Blog” resultType=”Blog”>
        SELECT * FROM BLOG WHERE state = ‘ACTIVE’
        <choose>
        <when test=”title != null”>
                AND title like #{title}
        </when>
        <when test=”author != null and author.name != null”>
                AND author_name like #{author.name}
        </when>
        <otherwise>
                AND featured = 1
        </otherwise>
        </choose>
</select>

(3) WHERE절의 구현: SQL문구에 where을 포함하면, 단순히 where만을 추가해서 "AND" 또는 "OR"가 인식되지 않는다. 그래서 where을 태그 형태로 열고/닫기를 하며 아래과 같이 작성해야한다.

<select id=”findActiveBlogLike”
        parameterType=”Blog” resultType=”Blog”>
        SELECT * FROM BLOG
        <where>
        <if test=”state != null”>
                state = #{state}
        </if>
        <if test=”title != null”>
                AND title like #{title}
        </if>
        <if test=”author != null and author.name != null”>
                AND author_name like #{author.name}
        </if>
        </where>
</select>

(4) set: 동적인 update문을 구현하기위해 사용할 수 있다.

<update id="update" parameterType="BbsDTO">
		UPDATE bbs
		<set>
			wname = #{wname},
			title = #{title},
			content = #{content},
			<if test="filesize > 0">
				filename = #{filename},
				filesize = #{filesize}
			</if>
		</set>
		WHERE bbsno = #{bbsno}
	</update>

(5) collection에 대해 반복처리: foreach

<select id="selectPostIn" resultType="domain.blog.Post">
        SELECT *
        FROM POST 
        WHERE ID in 
        <foreach item="item" index="index" collection="list"
        open="(" separator="," close=")">
                #{item}
        </foreach>
</select>

2. 특정 명령 구문별 사용되는 속성:

< SELECT >

resultType 속성
select문에서는 반드시 사용되어야하는 속성이다. select문 처리 후 DBMS로부터 받아오는 row 정보의 dataType을 지정하는 속성이기때문이다. 이 속성에 결과값을 매핑할 자바 객체 타입을 명시한다.

resultMap 속성
결과를 mapping할때에 하나의 java 객체로 mapping되지않고, list와 같은 collection 형태로 mapping되어야할때 사용된다. resultMap을 사용하려면 아래와 같이 select구문 위에 미리 resultMap의 mapping 규칙을 설정한다.

<resultMap id="selectResult" type="board">
     <result property="num" column = 'seq'>
     <result property="title" column = 'subject'>
     <result property="content" column = 'content'>
     <result property="redate" column = 'redate'>
</resultMap>
<select id=”selectBoard” parameterType=”int” resultMap=”selectResult”>
    SELECT * FROM board WHERE num = #{num}
</select>

< INSERT >, < UPDATE >, < DELETE>

parameterType 속성
위 select문에서와 동일하게 사용된다.
< selectKey >를 사용해서 생성된 키를 가져올수도 있다.

<insert id="insertAuthor" parameterType="domain.blog.Author">
        <selectKey keyProperty="id" resultType="int" >
                select board_seq.nextval as idfrom dual
        </selectKey>
        insert into Author (id,username,password,email,bio)
        values (#{id},#{username},#{password},#{email},#{bio})
</insert>
 
<update id="updateAuthor" parameterType="domain.blog.Author">
        update Author set
        username = #{username},
        password = #{password},
        email = #{email},
        bio = #{bio}
        where id = #{id}
</update>
 
<delete id="deleteAuthor” parameterType="int">
        delete from Author where id = #{id}
</delete>


References
  1. JPA 개념, JPA 프로젝트 from lectureblue

  2. Mybatis 프레임워크 특징 from lectureblue

  3. JPA와 MyBatis의 차이

  4. MyBatis-Hibernate from Tutorialspoint

좋은 웹페이지 즐겨찾기