Dev/SpringBoot

[SpringBoot] JPA 퀵 스타트

창문닦이 2019. 12. 23. 21:12

스프링과 JPA

데이터베이스에 연동되는 기술 : 전통적인 JDBC, 스프링 DAO, Mybatis, Hibernate 등 굉장히 다양
ORM : 애플리케이션에서 사용하는 SQL 까지도 프레임워크에서 제공
ORM들을 보다 쉽게 사용할 수 있도록 표준화시킨 것이 JPA(Java Persistence API)
스프링 데이터 JPA : 스프링 부트에서 JPA를 쉽게 사용할 수 있도록 지원하는 모듈

 

[JPA 개념 이해하기]

Mybatis : SQL을 개발자가 직접 XML 파일에 등록하여 사용

Hibernate(와 같은 ORM) : 프레임워크에서 SQL을 생성하기 때문에 개발자가 신경 쓸 필요 없음

 

- SQL을 직접 다루는 기술

JDBC를 이용하여 DB연동 처리 시 > 드라이버 클래스 로딩 > 커넥션 연결 > statement 생성 > SQL 전송 및 결과 처리 > 커넥션 연결 해제 등 반복적으로 필요하다.

이런 반복적인 작성들을 생략할 수 있게 도와주는 것이 프레임워크. 하지만 유지보수시에 SQL이 변경되는 것은 반영해야한다. 이렇게 수정된 SQL을 사용하는 자바 코드들도 더불어 반영되어야 한다. 

 

- SQL을 직접 다루지 않는 기술

Hibernate와 같은 ORM은 VO가 가지고 있는 정보를 데이터베이스가 아닌 java.util.Map 같은 컬렉션에 저장하는 것과 동일한 개념이다. 객체를 Map에 저장하므로 관련된 SQL이 사용되지 않는다. 추가적인 필드가 생기더라도 변수만 추가해주면 된다.

 

JPA란?

JPA는 마치 JDBC 프로그램에서 JDBC API와 같은 개념

JPA가 제공하는 인터페이스를 이용하여 데이터베이스를 처리하면 실제로는 JPA를 구현한 구현체가 동작하는 것. JPA를 구현한 구현체는 하이버네이트, EclipseLink, DataNucleus 등 이 존재. 스프링 부트에서는 기본적으로 하이버네이트를 JPA구현체로 이용.

 

JPA 동작원리

JPA는 자바 애플리케이션과 JDBC 사이에 존재하면서 JDBC의 복잡한 절차를 대신 처리해준다. JPA가 데이터베이스에 연동에 사용되는 코드뿐만아니라 SQL까지 제공한다.

 > 따라서 JPA를 이용해서 데이터베이스 연동을 처리하면 개발 및 유지보수의 편의성이 극대화. 

 

JPA 실습을 위한 H2 데이터베이스 설치

H2 데이터베이스는 스프링부트가 기본적으로 지원한다. 자바로 만들었으며 용량이 적고 실행 속도가 빠른 오픈소스. 일반적인 JDBC도 지원하고 인메모리, 서버 모드를 모두 지원한다. 브라우저 기반의 관리 콘솔까지 제공하기 때문에 테스트용 데이터베이스로 사용하기 적합하다.

다운로드 : http://h2database.com/html/main.html

 

설치후 h2/bin/h2w.bat 파일을 실행하면 데이터베이스가 구동되면서 다음과 같은 브라우저 기반의 관리 콘솔이 제공
웹 콘솔 연결하기

로그인 시 jdbc:h2:tcp://localhost/~/test 로 접속했다가 자꾸 연결이 되지 않는 오류가 발생했다.

해결방안 jdbc:h2:~/test로 한 번만 연결을 해주면 데이터베이스 파일이 생성되면서 연결. 이후에는 jdbc:h2:tcp://localhost/~/test 로 접속한다. (파일에 직접 접근하는 방식은 파일에 락이 걸려서 여러 곳에서 접속을 못하는 문제 발생할 수 있음)

출처: <https://www.inflearn.com/course/ORM-JPA-Basic/lecture/21684>

[JPA 퀵 스타트]

JPA 프로젝트 만들기

1. JPA Project 템플릿을 이용해도 되지만, JPA 프로젝트는 추가로 설정해야 되는 것들이 있으므로 메이븐 프로젝트로 생성해서 실습 진행.

2. 프로젝트 설정 변경 properties > project facets > JPA 체크

그런데 JPA 로 컨버팅 하려 하니 JPA가 조회가 되지 않는 오류 발생.... stackoverflow를 통해 찾아서 해결했다.

원래는 STS/이클립스에서 Persistence 관련 모듈이 기본적으로 제공되었었는데 치명적인 결함(?)을 발견해서 제외하고 배포된다고 한다. 그래서 사용하려면 아래 툴을 추가적으로 설치를 해주어야 한다.  

참조
Eclipse : Not able to create or convert a project in to JPA Project
https://stackoverflow.com/questions/32111999/eclipse-not-able-to-create-or-convert-a-project-in-to-jpa-project/40411853
How to Enable the JSF facet in eclipse user library
https://stackoverflow.com/questions/8145364/how-to-enable-the-jsf-facet-in-eclipse-user-library
Spring Tools Suits : JPA project and the JPA facets 
https://stackoverflow.com/questions/42869652/spring-tools-suite-jpa-project-and-the-jpa-facet/42909260#42909260

Help -> Install New Software -> work with에 해당하는 이클립스/sts 버전 선택 후 Persistence Tool  검색하여 설치

Persistence Tool에 해당하는 다섯개의 모듈을 선택해서 설치해줬다.
프로젝트의 properties를 다시 확인하면 변환이 가능하도록 조회된다 ! 

3. 메이븐 프로젝트를 jpa 프로젝트로 변경하면 다음과 같이 jpa 환경설정 파일인 persistence.xml 파일이 생성된다.

META-INF 하위에 생긴 영속성 파일

3. pom.xml 의존성 추가

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.studyboot</groupId>
  <artifactId>Chapter04</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>Chapter04</name>
  <url>http://maven.apache.org</url>

  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    
    <dependency>
	   <groupId>org.springframework.data</groupId>
	   <artifactId>spring-data-jpa</artifactId>
	   <version>1.9.0.RELEASE</version>
	</dependency>
	<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
	<dependency>
	    <groupId>com.h2database</groupId>
	    <artifactId>h2</artifactId>
	    <version>1.4.197</version>
	    <scope>test</scope>
	</dependency>
	<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
	<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-entitymanager</artifactId>
	    <version>5.1.0.Final</version>
	</dependency>
  </dependencies>
</project>

그리고 DB 연동 시 드라이버 클래스가 담긴 jar파일 해당 경로로도 반영해야 한다!

이거 안 해서 계속 커넥션이 제대로 생성 안됐다. 구글링 하다가Don't forget to copy your JDBC Driver to the lib/directory 라는 주석을 보고 기억났다.

C:\Program Files\Java\jdk1.8.0_191\jre\lib\ext

C:\Program Files\Java\jre1.8.0_191\lib\ext

출처: <https://wiper2019.tistory.com/45?category=776244>

4.  엔티티 클래스 작성 및 테이블 매핑

JPA는 테이블이 없으면 자바 클래스를 기준으로 매핑할 테이블을 자동으로 생성.

엔티티 : 테이블과 매핑되는 자바 클래스

  • 일반적인 VO클래스처럼 테이블과 동일한 클래스 이름을 사용하고 칼럼과 매핑될 멤버 변수를 선언하면 된다.

  • 이클립스에서 JPA Perspective가 제공하는 엔티티 생성 기능 이용 시 엔티티 생성과 함께 영속성 설정 파일(persistence.xml)에 자동으로 엔티티 등록.

@Entity
@Table(name = "BOARD")
public class Board implements Serializable {
	private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue
    private Long seq;
    private String title;
    private String writer;
    private String content;
    private Date createDate;
    private Long cnt;
    // getter, setter 생략

    @Override
    public String toString() {
    StringBuffer sb = new StringBuffer("Board [");
    sb.append("seq=").append(seq)
    .append(", title=").append(title)
    .append(", writer=").append(writer)
    .append(", content=").append(content)
    .append(", createDate=").append(createDate)
    .append(", cnt=").append(cnt)
    .append("]");
    return sb.toString();
    }
}

 

 

엔티티 매핑에 사용되는 어노테이션

@Entity : 엔티티 클래스. 기본적으로 클래스 이름과 동일한 테이블에 매핑

@Table : 엔티티 이름과 매핑될 테이블 이름이 다른 경우, name속성을 사용하여 매핑한다. 엔티티 이름=테이블 이름 일 땐 생략 가능

@Id : 테이블의 기본 키를 매핑한다. 예제에서는 seq 변수가 테이블의 SEQ 칼럼과 매핑되도록 설정. 엔티티의 필수 어노테이션으로서 @Id가 없는 엔티티는 사용하지 못한다.

@GeneratedValue : @Id가 선언된 필드에 기본 키 값을 자동으로 할당한다. 다양한 옵션이 있지만 @GeneratedValue만 사용하면 설정된 데이터베이스에 따라서 JPA가 자동으로 결정해준다. (H2는 시퀀스를 이용하여 처리한다.)

엔티티와 테이블을 보다 정교하게 매핑하기 위해서는 더 많은 어노테이션들이 필요.

 

5. JPA 메인 설정 파일 작성 - persistence.xml

JPA는 무조건 persistence.xml  파일을 로딩하여 이 파일의 설정되고 동작한다.

이 파일에는 영속성 유닛(Persistence Unit)이 설정되어 있다.

영속성 유닛(Persistence Unit)에 JPA가 연동할 데이터베이스에 대한 정보가 들어있다

<?xml version="1.0" encoding="UTF-8"?>
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="Chapter04">
    <class>com.studyboot.domain.Board</class>
    <properties>
        <!-- 필수속성 -->
        <!-- Don't forget to copy your JDBC driver to the lib/ directory -->
        <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
        <property name="javax.persistence.jdbc.user" value="sa"/>
        <property name="javax.persistence.jdbc.password" value=""/>
        <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />

        <!-- 옵션 -->
        <property name="connection.pool_size" value="1" />
        <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.hbm2ddl.auto" value="create" /> 
    </properties>
</persistence-unit>
</persistence>

등록 기능 테스트

public class JPAClient {
    public static void main(String[] args) {
        //EntityManager 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
        EntityManager em = emf.createEntityManager();

        try {
        Board board = new Board();
        board.setTitle("JPA 제목");
        board.setWriter("관리자");
        board.setContent("JPA 글 등록을 해보자");
        board.setCreateDate(new Date());
        board.setCnt(0L);

        em.persist(board);
        } catch (Exception e) {
        e.printStackTrace();
        } finally {
        em.close();
        emf.close();
        }
    }
}

데이터베이스가 구동되지 않아 오류 발생 시 h2w.bat 파일 실행 후 재시도하면 된다 

JPA 구현체로 H2Dialect를 썼기 때문에 하이버네이트가 H2에 최적화된 SQL을 생성한다

BOARD 테이블 생성 후 BOARD에 SEQ 칼럼 값을 자동으로 증가시키기 위해  hibernate_sequence 라는 시퀀스 자동 생성

> hibernate.hbm2ddl.auto가 create 이기 때문

콘솔 실행 결과

하지만 테이블에 INSERT 구문이 실행되지 않았음을 확인할 수 있다.

DB 조회시 입력되지 않은 것을 확인 가능

트랜잭션 관리

JPA가 실제 테이블에 등록/수정/삭제 작업을 처리하기 위해서는 해당 작업이 반드시 트랜잭션 안에서 수행되어야 한다.

트랜잭션이 시작하지 않았거나, 처리 이후에 정상적으로 종료되지 않으면 요청한 작업이 실제 데이터베이스에 반영되지 않는다.

정상적으로 등록 작업 처리 시 commit 실행

문제 발생 시 catch 블록에서 rollback 실행

public class JPAClient {
    public static void main(String[] args) {
        //EntityManager 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
        EntityManager em = emf.createEntityManager();

        //Transaction 생성
        EntityTransaction tx = em.getTransaction();

        try {
        //Transaction 시작
        tx.begin();

        Board board = new Board();
        board.setTitle("JPA 제목");
        board.setWriter("관리자");
        board.setContent("JPA 글 등록을 해보자");
        board.setCreateDate(new Date());
        board.setCnt(0L);

        //글 등록
        em.persist(board);

        //Transaction commit
        tx.commit();
        } catch (Exception e) {
        e.printStackTrace();
        //Transaction rollback
        tx.rollback();
        } finally {
        em.close();
        emf.close();
        }
    }
}

트랜잭션 생성 후 재실행하면 성공적으로 INSERT가 된 것을 확인 가능
DB 조회시 입력 반영 확인

데이터 누적하기

<property name="hibernate.hbm2ddl.auto" value="create" />의 설정값을 update로 변경한다.

<property name="hibernate.hbm2ddl.auto" value="update" />  

DB에 테이블을 새로 생성하는 것이 아니라 누적되서 반영된다.

 

 

검색 기능 테스트

데이터를 조회할 때는 EntityManager.find() 메서드 사용

검색 작업은 트랜잭션과 관련이 없으므로 관련 소스를 생략 가

실행 결과를 통해 이전에 저장한 1번 게시글이 잘 조회는 것을 볼 수 있다.

public class JPAClient {
    public static void main(String[] args) {
        //EntityManager 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
        EntityManager em = emf.createEntityManager();

        try {
        //글 상세 조회
        Board searchBoard = em.find(Board.class, 1L);
        System.out.println(">>>>>> " + searchBoard.toString());
        } catch (Exception e) {
        e.printStackTrace();
        } finally {
        em.close();
        emf.close();
        }
    }
}

 

성공적으로 엔티티를 검색한 결과를 출력했다.


[JPA 설정 - 영속성 유닛 설정]

 영속성 유닛 이름

  • JPA는 persistence.xml 파일로 복잡한 환경을 관리한다. 이 파일에는 데이터 소스를 비롯한 엔티티와 테이블 매핑을 위한 다양한 정보들이 설정되어 있다.

  • persistence.xml 파일은 <persistence>가 루트다. 이 태그는 영속성 유닛에 해당하는 <persistence-unit> 엘리먼트를 가진다.

  • 영속성 유닛은 연동하려는 데이터베이스당 하나씩 설정한다. 연동하려는 DB가 여러 개면 <persistence-unit> 를 여러 개 설정. 단, 식별을 위해 name 속성으로 유일한 값을 지정해줘야 한다.

  • 영속성 유닛 설정 시 애플리케이션에서는 영속성 유닛 설정을 로딩해서 EntityManagerFactory 생성 가능.

  • 애플리케이션에서 JPA 이용 시 EntityManager 객체 필요.

  • EntityManagerFactory에서 EntityManager를 얻을 수 있다.

영속성 유닛 설정(Persistence Unit 설정) : <persistence-unit name="Chapter04">

//JAVA 소스 
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
EntityManager em = emf.createEntityManager();
참조
http://www.java2s.com/Code/Java/JPA/CreateEntityManagerFactory.htm
https://kihoonkim.github.io/2017/01/27/JPA(Java%20ORM)/2.%20JPA-%EC%98%81%EC%86%8D%EC%84%B1%20%EA%B4%80%EB%A6%AC/

 

 엔티티 클래스 등록

  • 영속성 유닛 설정에서 가장 먼저 등록되는 정보가 엔티티 클래스 목록. 엔티티 클래스를 만드는 순간 자동으로   persistence.xml에 등록된다. (자동으로 안되면 직접 하면 된다.)

  • 스프링이나 J2EE 환경에서 JPA 사용 시 @Entity 어노테이션이 붙어있으면 자동으로 스캔하여 처리해서 명시적으로 안 써도 되지만, JPA 단독으로 사용하는 경우 엔티티 클래스들을 영속성 유닛에 등록해야 한다.

<persistence-unit name="Chapter04">
<class>com.studyboot.domain.boot<class>

데이터 소스 설정

  • JPA 구현체는 이 데이터소스 설정을 참조하여 특정 데이터베이스와 커넥션을 연결할 수 있다.

<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> JDBC 드라이버 클래스
<property name="javax.persistence.jdbc.user" value="sa"/> 데이터베이스 아이디
<property name="javax.persistence.jdbc.password" value=""/> 데이터베이스 비밀번호
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>  JDBC URL 정보

생략

Dialect 클래스 설정

  • JPA의 가장 큰 장점은 DB 연동에 필요한 SQL 구문을 자동으로 생성한다는 것.

  • 하지만 DB에 따라서 키 생성 방식도 다르고 지원되는 함수도 다르다.

  • JPA는 특정 DB에 최적화된 SQL을 생성하는데 어떤 Dialect가 설정되느냐에 따라 생성되는 SQL이 달라진다.

<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
JPA가 지원하는 다양한 Dialect 클래스를 doc을 통해 확인해보자
https://www.connect2java.com/tutorials/hibernate/what-is-dialect-class-in-hibernate/

JPA 구현체 설정

  • JPA는 다양한 ORM 프레임워크를 동일한 방법으로 사용하기 위한 인터페이스.

  • 따라서 JPA를 사용할 때 실질적으로 기능을 제공할 JPA 구현체에 대한 설정이 필수적으로 들어가야 한다.

<!-- 옵션 -->
<property name="hibernate.show_sql" value="true" /> 하이버네이트가 생성한 SQL을 콘솔에 출력
<property name="hibernate.format_sql" value="true" /> 하이버네이트가 생성 SQL을 출력할 때, 보기좋은 포맷으로 출력
<property name="hibernate.use_sql_comments" value="true" /> SQL에 포함된 주석도 같이 출력
<property name="hibernate.id.new_generator_mappings" value="true" />  키 생성 전략을 사용한다.
<property name="hibernate.hbm2ddl.auto" value="create" />  테이블 생성이나 변경, 삭제 같은 DDL 구문을 자동으로 실행할지 지정한다.
  • hibernate.id.new_generator_mappings 속성이 가장 중요. 테이블에 저장되는 모든 데이터는 다른 데이터와 식별할 수 있는 유일키(primary key)를 가지고 있어야 한다. 이 키 값을 자동으로 증가시키는 설정(true)

  • hibernate.hbm2ddl.auto 속성은 엔티티를 기준으로 테이블을 새롭게 생성할지 기존의 테이블을 재사용할지 등의 여부를 결정할 수 있다.

    • create : 애플리케이션을 실행할 때, 기존 테이블을 삭제하고 엔티티에 설정된 매핑 정보를 참조하여 새로운 테이블 생성 (drop - > create)

    • create-drop : create와 같지만, 애플리케이션이 종료되기 직전에 생성된 테이블을 삭제(drop -> create -> drop)

    • update : 기존에 사용 중인 테이블이 있으면 테이블을 생성하지 않고 재사용한다. 없을 때만 새롭게 생성한다. 만약 엔티티 클래스의 매핑 설정이 변경되면 변경된 내용만 반영(ALTER)

참조
https://ultrakain.gitbooks.io/jpa/chapter2/chapter2.5.html
https://stackoverflow.com/questions/2067526/hibernate-connection-pool

[JPA 설정 - 엔티티 매핑 설정]

엔티티 매핑은 엔티티들의 관계를 매핑하는 연관관계 매핑이 다소 복잡하므로 쉽지 않다.

 

@Entity & @Id

  • @Entity는 자바 클래스를 JPA가 관리하는 엔티티로 인식하게 하는 어노테이션

  • @Id는 엔티티로부터 생성된 객체는 반드시 다른 객체와 식별할 수 있도록 도와주는 역할.

  • JPA는 @Id를 이용해서 식별자 필드를 매핑한다.

  • 테이블에 저장된 각 로우는 PK칼럼을 통해 유일한 데이터로 식별.

  • 식별자 필드 : PK칼럼과 매핑될 식별자

@Table

  • 엔티티 이름과 테이블 이름이 다른 경우 @Table을 이용해서 매핑할 테이블 이름을 정확히 지정해야 한다.

  • 다양한 속성을 가질 수 있음.

    • name : 매핑할 테이블 이름을 지정

    • catalog : 데이터베이스 카탈로그를 지정

    • schema : 데이터베이스 스키마를 지정

    • uniqueConstraints : 유일 키 제약조건. 결합 unique 제약조건을 지정하며, 여러 개의 칼럼이 결합되어 유일성을 보장해야 하는 경우 사용한다.

@Column

  • 엔티티의 변수와 테이블의 칼럼을 매핑할 때 사용한다. 엔티티 변수명과 칼럼 이름이 다를 때 사용한다.

  • 일반적으로 @nullable 자주 사용

  • 다양한 속성을 가질 수 있음.

    • name : 칼럼 이름을 지정. 생략 시 프로퍼티명과 동일하게 매핑

    • unique : unique 제약조건 추가(기본값 : false)

    • nullable : null 상태 허용 여부 설정(기본값 : false)

    • insertable : INSERT를 생성할 때 이 칼럼을 포함할 것인지(기본값 : true)

    • updatable : UPDATE를 생성할 때 이 컬럼을 포함할 것인지(기본값 : true)

    • coluimnDefinition :  이 칼럼에 대한 DDL 문을 직접 기술

    • length : 문자열 타입의 칼럼 길이를 지정(기본값 : 255)

    • precision :  숫자 타입의 전체 자릿수를 지정(기본값 : 0)

    • scale : 숫자 타입의 소수점 자릿수를 지정(기본값 : 0)

@Temporal

  • java.util.Date 타입의 날짜 데이터를 매핑할 때 사용.

  • TemporalType을 이용하여 날짜의 형식을 지정할 수 있다.

    • TemporalType.DATE(날짜만 출력), TemporalType.TIME(시간만 출력), TemporalType.TIMESTAMP(날짜와 시간 출력) 

@Transient

  • 엔티티 클래스 내의 특정 변수를 영속 필드에서 제외할 때 사용.

  • 이 어노테이션이 붙은 필드는 테이블에 매핑되는 칼럼이 없으며, 변수에 저장된 값을 테이블에 저장할 필요도 없다.

  • 아무런 설정도 하지 않으면 JPA는 자동으로 변수에 해당하는 칼럼을 찾아 매핑 처리하므로 이 어노테이션을 붙임. 


[JPA 설정 - 식별자 값 자동 증가 설정] 

  • JPA가 엔티티를 이용하여 등록작업을 처리할 때, 식별자 필드에 값이 설정되지 않으면 예외가 발생.

  • 식별자 필드에는 반드시 유니크한 값이 설정되어야 한다.

  • 식별자 필드 위에 자동으로 증가된 값을 할당할 때는 @GeneratedValue 어노테이션을 사용하면 된다.

    • GenerationType.TABLE : 하이버네이트가 테이블을 사용해서 pk 값을 생성한다. 따라서 PK 값 생성만을 위한 별도의 테이블이 필요하다.

    • GenerationType.SEQUENCE : 시퀀스를 사용해서 PK 값을 생성한다. 이 전략은 시퀀스를 지원하는 데이터베이스에서만 사용할 수 있다.

    • GenerationType.IDENTITY : auto_increment나 IDENTITY를 이용하여 PK값을 생성한다. 일반적으로 MySQL 같은 데이터베이스를 이용할 때 사용한다.

    • GenerationType.AUTO : 하이버네이트가 데이터베이스에 맞는 PK 값 생성 전략을 선택한다(기본적인 설정값)

    • strategy : 자동 생성 전략을 선택한다 (GenerationType 지정)

    • generator : 이미 생성된 키 생성기를 참조한다.

 테이블 전략 사용하기

@Entity
@TableGenerator(name = "BOARD_SEQ_GENERATOR",
table = "ALL_SEQUENCES",           //ALL_SEQUENCES 라는 키 생성 테이블을 만듦
pkColumnValue = "BOARD_SEQ",       //BOARD_SEQ 이름으로 증가되는 값을 저장
initialValue = 0,                  //처음 저장되는 번호는 0
allocationSize = 1)                //ALL_SEQUENCES 테이블에서 값을 한 번 꺼내 쓸 때마다 자동으로 1씩 증가
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
    private Long seq; // 식별자 생성 전략을 GenerationType.TABLE 로 지정, BOARD_SEQ_GENERATOR 키 생성기 참조
    private String title;
    private String writer;
    private String content;
    @Temporal(TemporalType.DATE) //년월일자만 저장
    private Date createDate;
    private Long cnt;

    @Transient //영속 필드에 제외시 사용
    private String searchConfition;
    @Transient
    private String searchKeyword;
    // 생략
}

테이블/시퀀스 존재하면 삭제 후 생성
성공적으로 처리된 결과를 확인 가능

1. 시퀀스 테이블 조회 (최초 생성이므로 조회 데이터 없음) 
2. 검색 결과가 없으므로 시퀀스 테이블에 1 insert 
3. 일련번호를 증가시키기 위한 update 
4. 1단계에서 조회한 시퀀스 값을 seq에 설정해서 insert 처리

시퀀스 테이블과 board 테이블이 생성된 것을 조회

 

 

 

시퀀스 전략 사용하기

    • 시퀀스 전략은 테이블 전략과 유사하지만 유일 키를 생성하기 위해 테이블이 아닌 시퀀스를 사용하는 것이 다르다.

@Entity
@SequenceGenerator(name = "BOARD_SEQ_GENERATOR2",
sequenceName = "BOARD_SEQUENCE",          //BOARD_SEQUENCE 시퀀스 생성
initialValue = 1,                         //처음 저장되는 번호는 1
allocationSize = 1)                       //한 번 꺼내 쓸 때마다 자동으로 1씩 증가
public class Board2 {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR2")
    private Long seq; // 식별자 생성 전략을 GenerationType.SEQUENCE 로 지정, BOARD_SEQ_GENERATOR2 키 생성기 참조
    private String title;
    private String writer;
    private String content;
    @Temporal(TemporalType.DATE)
    private Date createDate;
    private Long cnt;

    @Transient //영속 필드에 제외시 사용
    private String searchConfition;
    @Transient
    private String searchKeyword;
    //생략
}
Hibernate:
drop table Board2 if exists
Hibernate:
drop sequence if exists BOARD_SEQUENCE
Hibernate: create sequence BOARD_SEQUENCE start with 1 increment by 1    @SequenceGenerator를 이용해서 시퀀스 생성
Hibernate:
create table Board2 (
     seq bigint not null,
     cnt bigint,
     content varchar(255),
     createDate date,
     title varchar(255),
     writer varchar(255),
     primary key (seq)
)
 
Hibernate:
call next value for BOARD_SEQUENCE
Hibernate:
insert
into
     Board2
     (cnt, content, createDate, title, writer, seq)
values
     (?, ?, ?, ?, ?, ?)

테이블 전략을 사용했을 때 보다 등록작업이 좀 더 간결하다.

  • 테이블 전략과 시퀀스 전략은 장단점이 서로 상충한다.

    • 테이블 전략
      • 데이터베이스에 무관하게 범용적으로 사용할 수 있음

      • 별도 테이블을 생성해야 하고 키 값을 자동으로 증가시키기 위해 별도의 UPDATE 작업을 수행하는 등 성능상의 문제

    • 시퀀스 전략
      • 별도의 테이블이 필요 없으며 등록 작업의 처리 속도도 빠르다

      • 오라클이나 H2 데이터베이스 같은 시퀀스를 지원하는 DB에서만 사용이 가능하다.

 

자동 전략 사용하기

    • 연동하는 DB와 무관하게 키 생성 전략을 사용하려면 식별자 필드에 @GeneratedValue 만 사용하면 기본값  AUTO가 적용된다.

@Entity
public class Board3 {
    @Id
    @GeneratedValue // 식별자 필드에 @GeneratedValue만 사용하면 기본 값인 AUTO 적용
    private Long seq;
    private String title;
    private String writer;
    private String content;
    @Temporal(TemporalType.DATE)
    private Date createDate;
    private Long cnt;

    @Transient //영속 필드에 제외시 사용
    private String searchConfition;
    @Transient
    private String searchKeyword;
    //생략
 }

변경 후 실행 시 결과 화면

Hibernate: create sequence hibernate_sequence start with 1 increment by 1

Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
     Board3
     (cnt, content, createDate, title, writer, seq)
values
     (?, ?, ?, ?, ?, ?)
  • h2 데이터베이스는 시퀀스를 지원하므로 다음과 같이 hibernate_sequence라는 이름의 시퀀스를 생성하여 사용한다.


 

[JPA API 이해 - EntityManagerFactory, EntityManager]

애플리케이션에서 JPA를 이용해서 CRUD 기능을 처리하려면 EntityManager객체를 사용해야 한다. 즉, 애플리케이션의 시작 = EntityManager 생성이다. 그리고 EntityManager는 EntityManagerFactory에서 얻어진다.

  1. Persistence 클래스를 이용하여 영속성 유닛(persistence-unit) 정보가 저장된 JPA 메인 환경 설정 파일 로딩 (PA가 META-INF 폴더의 persistence.xml 로딩)

  2. 영속성 유닛(persistence-unit) 정보를 바탕으로 EntityManagerFactory객체 생성

  3. EntityManagerFactory로부터 EntityManager를 얻어 DB 연동 처리

//JAVA 소스
public class JPAClient {
    public static void main(String[] args) {
        //EntityManager 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
        EntityManager em = emf.createEntityManager();
    }
}

EntityManager의 CRUD 메서드

persist : 엔티티를 영속화한다(INSERT)

merge : 준영속 상태의 엔티티를 영속화한다(UPDATE)

remove : 영속 상태의 엔티티를 제거한다(DELETE)

find : 하나의 엔티티를 검색한다(SELECT ONE)

createQuery : JPQL에 해당하는 엔티티 목록을 검색한다 (SELECT LIST)

 

[JPA API 이해 - 영속성 컨텍스트와 엔티티 상태]

 1. 비영속(NEW) : 엔티티가 영속성 컨텍스트와 전혀 무관한 상태

  • 엔티티 객체만 생성되고 아직 엔티티를 영속성 컨텍스트에 저장하지 않은 상태

2. 영속(MANAGED) : 엔티티가 영속성 컨텍스트에 저장된 상태

  • EntityManager를 통해 엔티티가 영속성 컨텍스트에 젖아된 상태.

  • EntityManager.persist() 메소드 사용

  • EntityManager.find() 메소드 사용

    • 조회하고자 하는 엔티티가 영속성 컨텍스트에 있으면 해당 엔티티 반환.

    • 조회하고자 하는 엔티티가 없다면 데이터베이스에서 데이터를 조회하여 새로운 엔티티 객체를 생성해서 영속성 컨텍스트에 저장

3. 준영속(DETATCHED) : 엔티티가 한번 영속성 컨텍스트에 저장되었다가 분리된 상태

  • 한번 영속성 컨텍스트에 들어간 엔티티가 어떤 이유에서 영속성 컨텍스트에서 벗어난 상태.

  • 벗어난 상태 이므로 준영속 상태의 엔티티는 값을 수정해도 DB에 아무런 영향을 주지 않는다.

  • 준영속 상태의 엔티티는 모두 완전히 사라진 게 아니기 때문에 merge()로 다시 영속 상태로 전환 가능하다.

  • EntityManager.detach(entity) : 특정 엔티티만 준영속 상태로 전환

  • EntityManager.clear() : 영속성 컨텍스트를 초기화한다. 영속성 컨텍스트가 관리하던 엔티티들을 모두 삭제.

  • EntityManager.close() : 영속성 컨텍스트를 종료한다. 영속성 컨텍스트는 종료되기 직전에 자신이 관리하던 엔티티들을 모두 삭제.

4. 삭제(REMOVED) : 엔티티가 영속성 컨텍스트에서 삭제된 상태

  • 엔티티가 영속성 컨텍스트에서도 제거되고 테이블 데이터도 삭제된 상태.

  • 일반적으로 삭제된 엔티티는 재사용하지 않고 가비지 컬렉션으로 회수되도록 내버려 둔다

  • EntityManager.remove()

 

영속성 컨텍스트와 1차 캐시

  • JPA는 persist() 메소드를 호출했다고 해서 바로 테이블에 INSERT 진행하지 않음

  • 영속성 컨텍스트 내부에 1차 캐시를 사용하기 때문.

persist메소드

  1. EntityManager.persist() 를 통해 엔티티 영속화 진행

  2. 엔티티는 영속성 컨텍스트가 가지고 있는 1차 캐시에 등록

  3. 1차 캐시는 일종의 Map과 같은 컬렉션(key와 value로 엔티티를 관리)

  4. KEY : @ID 로 매핑한 식별자 값, VALUE : 엔티티 객체

  5. 1차 캐시에 저장된 엔티티는 바로 실제 DB에 반영되지 않는다.

  6. EntityTransaction으로 트랜잭션을 종료할 때 실제 데이터 베이스에 반영된다.

commit메소드를 통해 트랜잭션 종료 시

  1. 트랜잭션 객체의 commit메소드를 호출

  2. 1차 캐시에 저장된 엔티티에 해당하는 INSERT구문이 생성되고 DB로 전송

이렇게 영속성 컨텍스트에 저장된 엔티티를 DB에 반영하는 과정을 Flush라 한다.

 

[JPA API 이해 - 영속성 컨텍스트와 SQL 저장소]

엔티티 저장하기

영속성 컨텍스트는 1차 캐시뿐만 아니라 SQL 저장소라는 것도 가지고 있다.

  1. board 엔티티를 persist()메소드로 영속성 컨텍스트 저장

  2. 엔티티를 1차 캐시로 등록

  3. 1차 캐시에 등록된 엔티티에 해당하는 INSERT 구문을 생성하여 SQL 저장소에 등록

  4. commit 메소드로 트랜잭션이 종료

  5. SQL저장소에 저장되었던 모든 SQL들이 한꺼번에 DB로 전송

결과적으로 한 번의 DB 통신으로 SQL 구문을 한꺼번에 처리 가능. (성능 최적화가 가능. 마치 JDBC 배치 업데이트 같은 개념)

 

엔티티 수정과 스냅샷

public class JPAClientUpdate {
	public static void main(String[] args) {
		//EntityManager 생성
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
		EntityManager em = emf.createEntityManager();

		//글 수정
		//Transaction 생성
		EntityTransaction tx = em.getTransaction();
		
		try {
			//Transaction 시작
			tx.begin();
			//수정할 엔티티 조회
			Board3 board = em.find(Board3.class, 1L);
			board.setTitle("검색한 게시글의 정보를 수정");
		
			//Transaction commit
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			//Transaction rollback
			tx.rollback();
		} finally {
			em.close();
			emf.close();
		}	
	}
}
  1. 엔티티 수정을 위해서는 수정할 엔티티가 반드시 영속성 컨텍스트에 존재해야 한다. 없을 경우 예외 발생.

  2. 검색된 엔티티는 1차 캐시에 저장

  3. 엔티티 변수 값을 수정

  4. JPA는 수정된 변수들을 찾아 UPDATE 구문을 작성해서 SQL 저장소에 저장

  5. 트랜잭션 종료 시 UPDATE 처리

Hibernate:
select
     board3x0_.seq as seq1_2_0_,
     board3x0_.cnt as cnt2_2_0_,
     board3x0_.content as content3_2_0_,
     board3x0_.createDate as createDa4_2_0_,
     board3x0_.title as title5_2_0_,
     board3x0_.writer as writer6_2_0_
from
     Board3 board3x0_
where
     board3x0_.seq=?
Hibernate:
update

     Board3
set
     cnt=?,
     content=?,
     createDate=?,
     title=?,
     writer=?
where
     seq=?

  • 스냅샷 : JPA는 검색된 엔티티를 영속성 컨텍스트에 저장할 때, 엔티티 복사본을 만들어서 별도의 컬렉션에 저장하는데 이 저장 공간을 의미.

  • 엔티티 수정에서의 JPA 기본 전략은 모든 필드 수정.

    • 그래서 한 컬럼만 수정되었는데도 모든 칼럼이 수정되는 SQL 이 실행됨

    • 수정 쿼리가 항상 같기 때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용 가능

    • DB에 동일한 쿼리를 보내면 DB는 이전에 한 번 파싱 했던 쿼리를 재사용하므로 성능상의 이점

 

엔티티 삭제하기

  • 수정과 마찬가지로 영속성 컨텍스트에 삭제할 엔티티가 없다면 예외 발생

public class JPAClientDelete {
	public static void main(String[] args) {
		//EntityManager 생성
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
		EntityManager em = emf.createEntityManager();

		//글 삭제
		//Transaction 생성
		EntityTransaction tx = em.getTransaction();
		
		try {
			//Transaction 시작
			tx.begin();
			
			//엔티티 조회
			Board3 board = em.find(Board3.class, 1L);
			board.setSeq(1L);
			
			//게시글 삭제
			em.remove(board);
			
			//Transaction commit
			tx.commit();
		} catch (Exception e) {
			e.printStackTrace();
			//Transaction rollback
			tx.rollback();
		} finally {
			em.close();
			emf.close();
		}	

	}
}

 

목록 검색과 JPQL

테이블에 저장된 특정 데이터를 상세 조회하려는 경우 EntityManager의 find 메소드 사용

목록을 조회하기 위해서는 JPQL(Java Persistence Query Language)라는 JPA에서 제공하는 별도의 쿼리 명령어 사용

JPQL은 검색 대상이 테이블이 아니라 엔티티라는 점이 중요.

따라서 엔티티 이름과 엔티티가 가지고 있는 변수를 이용하여 쿼리를 구성해야 한다.

JPQL은 검색 기능을 수행하면 쿼리를 실행하기 전에 sql 저장소에 저장되어 있던 모든 sql 구문들을 db로 전송한다.

그래야 영속성 컨텍스트에 없는 데이터를 db로부터 조회해서 영속성 컨텍스트에 등록할 수 있기 때문이다.

public class JPAClientSelectList {
	public static void main(String[] args) {
		//EntityManager 생성
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("Chapter04");
		EntityManager em = emf.createEntityManager();
		
		//Transaction 생성
		EntityTransaction tx = em.getTransaction();
		
		try {
			//Transaction 시작
			tx.begin();
			Board3 board = new Board3();
			board.setTitle("어깨아파");
			board.setWriter("관리자");
			board.setContent("스트레칭을 해주자");
			board.setCreateDate(new Date());
			board.setCnt(0L);
		
			//글 등록
			em.persist(board);
			
			//Transaction commit
			tx.commit();
			
			//글 목록조회
			String jpql = "select b from Board3 b order by b.seq desc";
			List<Board3> boardList = em.createQuery(jpql,Board3.class).getResultList();
			for (Board3 brd : boardList) {
				System.out.println(">>> " + brd.toString());
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			//Transaction rollback
			tx.rollback();
		} finally {
			em.close();
			emf.close();
		}	
	}
}
Hibernate:
select
     board3x0_.seq as seq1_2_,
     board3x0_.cnt as cnt2_2_,
     board3x0_.content as content3_2_,
     board3x0_.createDate as createDa4_2_,
     board3x0_.title as title5_2_,
     board3x0_.writer as writer6_2_
from
     Board3 board3x0_
order by
     board3x0_.seq desc
>>> Board [seq=4, title=어깨아파, writer=관리자, content=스트레칭을 해주자, createDate=Thu Dec 26 21:09:02 KST 2019, cnt=0]
>>> Board [seq=3, title=비온당, writer=관리자, content=춥당, createDate=2019-12-26, cnt=0]
>>> Board [seq=2, title=JPA 제목, writer=관리자, content=JPA 글 등록을 해보자, createDate=2019-12-26, cnt=0]
참고 서적 : 누구나 끝까지 따라 할 수 있는 스프링 부트 퀵스타트