SlideShare a Scribd company logo
1 of 77
Download to read offline
HIBER NATE 5.x
시작하기
수익성의 성격을 띄지 않는다면
재배포, 수정 가능합니다.
visualkhh@gmail.com
https://github.com/visualkhh/book-hibernate
(2016. 12. 21)
목 차
ORM (Object Relational Mapping) 무엇인가?______________________________________________________________5
JPA (Java Persistence API) 무엇인가?_______________________________________________________________________5
HIBERNATE 무엇인가? ______________________________________________________________________________________5
왜 JPA 를 쓰는가? __________________________________________________________________________________________5
1. 기존 SQL 중심적인 개발시 불편하다 _________________________________________________________________5
2. 객체-관계 간 모델 불일치 ____________________________________________________________________________5
3. 상속 불일치____________________________________________________________________________________________6
4. 관계와 연관 관계의 불일치 ___________________________________________________________________________6
장단점_______________________________________________________________________________________________________6
장점_______________________________________________________________________________________________________6
단점_______________________________________________________________________________________________________6
JPA, HIBERNATE Architecture _____________________________________________________________________________7
엔티티 상태 및 생명주기___________________________________________________________________________________9
비영속 상태_______________________________________________________________________________________________9
영속 상태_________________________________________________________________________________________________9
준영속 상태_______________________________________________________________________________________________9
Hibernate 셋팅____________________________________________________________________________________________ 11
gradle___________________________________________________________________________________________________ 11
hibernate.cfg.xml _______________________________________________________________________________________ 13
Session ____________________________________________________________________________________________________ 14
Session API _____________________________________________________________________________________________ 14
SessionFactory__________________________________________________________________________________________ 14
Hibernate-provided BasicTypes___________________________________________________________________________ 15
Entity ______________________________________________________________________________________________________ 18
@Entity _________________________________________________________________________________________________ 18
식별자_____________________________________________________________________________________________________ 19
@GeneratedValue ______________________________________________________________________________________ 19
복합 식별자 ID (KEY) _____________________________________________________________________________________ 22
키선언 첫번째 방법 (@Embeddable) __________________________________________________________________ 23
키선언 두번째 방법 (@EmbeddedId) __________________________________________________________________ 24
키선언 세번째 방법 (@IdClass) ________________________________________________________________________ 25
Join 조인__________________________________________________________________________________________________ 26
연관 관계_______________________________________________________________________________________________ 26
다중성 ________________________________________________________________________________________________ 26
방향성 ________________________________________________________________________________________________ 26
조인전에 먼저 알아야될 Cascade______________________________________________________________________ 26
1:1 @OneToOne________________________________________________________________________________________ 27
방향성을 갖자________________________________________________________________________________________ 31
@OneToMany, @ManyToOne__________________________________________________________________________ 32
1:N, N:1, N:N 컬렉션 영속화 (List, Set, Map, Array[]) _______________________________________________ 32
방향성 갖자 __________________________________________________________________________________________ 43
조회하기______________________________________________________________________________________________ 43
@ManyToMany_________________________________________________________________________________________ 46
N:N 연결하기 _________________________________________________________________________________________ 49
캐싱 _______________________________________________________________________________________________________ 54
1 차 캐시 _______________________________________________________________________________________________ 54
2 차 캐시 _______________________________________________________________________________________________ 55
cache usage 속성 ______________________________________________________________________________________ 55
쿼리 캐시_______________________________________________________________________________________________ 56
상속 전략 _________________________________________________________________________________________________ 57
@Table-per-Class_______________________________________________________________________________________ 57
@Table-per-Subclass ___________________________________________________________________________________ 61
@Table-per-Concrete-Class ____________________________________________________________________________ 62
하이버네이트 질의어 _____________________________________________________________________________________ 63
Query Class 사용하기 __________________________________________________________________________________ 63
@Embedded Objects _____________________________________________________________________________________ 67
@ElementCollection ______________________________________________________________________________________ 68
Pagination 페이지네이션 _________________________________________________________________________________ 70
Criteria ____________________________________________________________________________________________________ 71
네임드 쿼리 _______________________________________________________________________________________________ 74
네이티브 쿼리_____________________________________________________________________________________________ 76
Groovy Template 이용하여 Dynamic Query 사용하기___________________________________________________ 77
ORM (Object Relational Mapping) 무엇인가?
RDB 테이블을 객체지향적으로 사용하기 위한 기술입니다. RDB 은 객체지향적 (상속, 다형성, 레퍼런스, 오브젝트
등)으로 접근하기 쉽지 않습니다.
때문에 ORM 을 사용해 오브젝트와 RDB 사이에 객체지향적으로 다루기 위한 기술입니다.
JPA (Java Persistence API) 무엇인가?
ORM 전문가가 참여한 EJB 3.0 스펙 작업에서 기존 EJB ORM 이던 Entity Bean 을 JPA 라고 바꾸고 JavaSE,
JavaEE 를 위한 영속성(persistence) 관리와 ORM 을 위한 표준 기술입니다. JPA 는 ORM 표준 기술로 Hibernate,
OpenJPA, EclipseLink, TopLink Essentials 과 같은 구현체가 있고 이에 표준 인터페이스가 바로 JPA 입니다.
HIBERNATE 무엇인가?
Boss 에서 개발한 ORM(Object Relational Mapping) 프레임워크 입니다.
장점
Hibernate 는 특정 클래스에 매핑되어야 하는 데이터베이스의 테이블에 대한 관계 정의가 되어 있는 XML 파일의
메타데이터로 객체관계 매핑을 간단하게 수행시킵니다.
Hibernate 를 사용하면 데이터베이스가 변경되더라도 SQL 스크립트를 수정하는등의 작업을 할 필요가 없습니다.
애플리케이션에서 사용되는 데이터베이스를 변경시키고자 한다면 설정파일의 dialect 프로퍼티를 수정함으로서
쉽게 처리할 수 있습니다.
Hibernate 는 MySQL, Oracle, Sybase, Derby, PostgreSQL 를 포함한 많은 데이터베이스를 지원하며 POJO 기반의
모델과도 원활하게 동작합니다.
왜 JPA 를 쓰는가?
1. 기존 SQL 중심적인 개발시 불편하다
- 쿼리가 변경되면 이에따른 프로그램 소스 DTO 객체의 변경도 불가피하게 일어난다
- 데이터를 가져와 객체지향적으로 관계를 Mapping 하는 일이 매번 일어난다.
!!SQL 의존적인 개발이 이루어진다.
2. 객체-관계 간 모델 불일치
관계형 데이터베이스에는 로우와 컬럼의 2 차원 형태로 데이터가 저장된다. 데이터 관계는 외래키 foreign key
형태로 표현된다. 문제는 도메인 객체를 관계형 데이터 베이스로 저장할 때 발생한다. 애플리케이션의 객체는
로우와 컬럼 형태가 아니다. 도메인 객체는 객체의 상태를 속성(변수)으로 가지고 있다. 그래서 도메인 객체
그대로 관계형 데이터베이스에 저장할 수가 없다. 이러한 불일치를 객체-관계 간 임피던스 불일치 object-
relational impedance mismatch 라고 합니다.
3. 상속 불일치
상속은 객체 세계에서는 지원하지만, 관계형 스키마에서는 지원하지 않는다. 상속은 모든 객체지향 언어, 특히
자바에서 바늘과 실처럼 뗄 수 없는 특징입니다. 안타깝게도 관계형 스키마에는 상속 개념이 없습니다. 회사에서
임원과 직원의 예를 들어보면. 임원 개인도 회사의 직원이죠. 이 관계를 데이터베이스에서 표현하는 것은 테이블
간 관계 수정이 필요해서 쉽지 않습니다. 상속 없이 현실 세계의 문제 상황을 표현하는 것은 매우 복잡한
일입니다. 그런데 데이터베이스는 상속 관계와 같은 형태를 알지 못하지요. 이것을 해결할 간단한 방법은없지만,
문제를 풀 수 있는 몇 가지 접근법이 있습니다. 이 접근법은 다양한 클래스-테이블 class-to-table 전략을
사용합니다.
4. 관계와 연관 관계의 불일치
1. SQL 중심적인 개발의 문제점
- field 하나추가시 쿼리도 바꿔야하고 VO도 바꿔야되고 ...
- SQL에 의존적인 개발을 피하기 어렵다.
- 객체답게 모델링 할수록 매핑 작업만 늘어난다
장단점
장점
- 객체지향적으로 데이터를 관리할 수 있기 때문에 비즈니스 로직에 집중 할 수 있으며,
객체지향 개발이 가능합니다.
- 테이블 생성, 변경, 관리가 쉽다. (JPA 를 잘 이해하고 있는 경우) 로직을 쿼리에 집중하기 보다는
객체자체에 집중 할 수 있습니다.
- 빠른 개발이 가능합니다.
단점
- 어렵다. 장점을 더 극대화 하기 위해서 알아야 할게 많습니다.
- 잘 이해하고 사용하지 않으면 독이될수도 있습니다.
- 성능상 문제가 있을 수 있다.(이 문제 또한 잘 이해해야 해결이 가능합니다.
JPA, HIBERNATE Architecture
Application Hibernate JPA JDBC API DB
Application JPA 인터페이스 Hibernate, EclipseLink, DataNucleus..등
...
엔티티 상태 및 생명주기
비영속 상태
퍼시스턴트 객체를 처음 만들었을 때의 상태. 데이터베이스 테이블에 관련 데이터가 없으며, 연관된 Session 이
없다.
영속 상태
현재 활성화된 Session 과 연결된 퍼시스턴트 객체. 이 상태의 퍼시스턴트 객체는 고유성을 가지며, 프로퍼티
값의 변경이 Session 을 통해 자동으로 데이터베이스에 반영된다.
준영속 상태
영속 상태의 퍼시스턴트 객체가 Session 과 연결이 끊기면 준영속 상태가 된다. Hibernate 의 관리를 받지는
않지만, 영속 데이터를 갖고 있다.
출처
https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html
http://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate
http://hibernate.org/search/documentation/getting-started/
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html
https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/session-configuration.html
http://docs.jboss.org/hibernate/orm/
http://www.slideshare.net/zipkyh/spring-datajpa?next_slideshow=1
http://www.javajigi.net/pages/viewpage.action?pageId=5924
http://javacan.tistory.com
Hibernate 셋팅
gradle
group 'com.khh'
version '1.0-SNAPSHOT'
apply plugin: 'java'
sourceCompatibility = 1.5
repositories {
mavenCentral()
}
dependencies {
/* slf4j */
compile(group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21')
/* hibernate */
compile(group: 'org.hibernate', name: 'hibernate-core', version: '5.2.5.Final')
/* lombok */
compile(group: 'org.projectlombok', name: 'lombok', version: '1.16.8')
/*groovy*/
compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.6'
/* class scaner */
compile (group: 'net.sf.corn', name: 'corn-cps', version: '1.1.7')
/* h2 database */
compile(group: 'com.h2database', name: 'h2', version: '1.4.193')
/* logback-classic */
compile(group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.8')
testCompile(group: 'junit', name: 'junit', version: '4.11')
}
build.gradle
Hibernate Setting (Properties Set)
package com.khh.hibernate.c0;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.h2.tools.Server;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import java.sql.SQLException;
@Data
@Slf4j
public class HibernateSetting implements Runnable{
Server server = null;
SessionFactory sessionFactory = null;
public HibernateSetting() throws SQLException {
//H2 Database Start
server = Server.createWebServer("-web", "-webAllowOthers", "-webPort", "8088").start();
//외부 XML 으로 설정
Configuration configuration = getHibernateConfigByCode();
//CODE BASE 로 설정
//Configuration configuration = getHibernateConfigByCode();
//Build
StandardServiceRegistryBuilder serviceRegistryBuilder = new
StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(serviceRegistryBuilder.build());
configuration.buildSessionFactory(serviceRegistryBuilder.build());
}
public Configuration getHibernateConfigByXML() {
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
return configuration;
}
public Configuration getHibernateConfigByCode() {
Configuration configuration = new Configuration();
configuration.setProperty(Environment.DIALECT, "org.hibernate.dialect.H2Dialect");
configuration.setProperty(Environment.DRIVER, "org.h2.Driver");
configuration.setProperty(Environment.URL, "jdbc:h2:mem:test");
configuration.setProperty(Environment.USER, "sa");
configuration.setProperty(Environment.POOL_SIZE, "55");
configuration.setProperty(Environment.STATEMENT_BATCH_SIZE, "30");
configuration.setProperty(Environment.AUTOCOMMIT, "true");
configuration.setProperty(Environment.SHOW_SQL, "true");
configuration.setProperty(Environment.FORMAT_SQL, "true");
configuration.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread");
configuration.setProperty(Environment.HBM2DDL_AUTO, "create-drop");
return configuration;
}
public void run() {
//log.debug("start");
}
public static void main(String[] arg) throws SQLException {
new HibernateSetting().run();
}
}
매개변수로 표준 가상머신 Standard VM 인수 형식을 사용할수 있다
-Dhibernate.connection.url=jdbc:derby:memory:JH;create=true
-Dhibernate.username=mk
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!--
http://www.tutorialspoint.com/hibernate/hibernate_configuration.htm
https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/session-configuration.html
-->
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
<property name="hibernate.connection.driver_class">org.h2.Driver</property>
<!-- <property name="hibernate.default_schema">user</property> -->
<!-- Assume test is the database name -->
<property name="hibernate.connection.url">jdbc:h2:mem:test</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>
<!-- JDBC connection pool -->
<property name="hibernate.connection.pool_size">55</property>
<property name="hibernate.jdbc.batch_size">30</property>
<property name="hibernate.connection.autocommit">true</property>
<!-- <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> -->
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<!-- <property name="hibernate.use_sql_comments">true</property> -->
<!-- current_session ( e.g. jta | thread | managed | custom.Class )-->
<property name="hibernate.current_session_context_class">thread</property>
<!-- hbm2ddl.auto ( e.g. validate | update | create | create-drop ) -->
<property name="hbm2ddl.auto">create-drop</property>
<!-- List of XML mapping files -->
<!-- <mapping resource="Employee.hbm.xml" /> -->
<!-- Infinispan 캐시 공급자 지정 -->
<property name="hibernate.cache.provider_class">
org.hibernate.cache.infinispan.InfinispanRegionFactory
</property>
</session-factory>
</hibernate-configuration>
Session
Session API
org.hibernate.SessionFactory 클래스에서 제공하는 SessionFactory는 Session 인스턴스를 생성하는 팩토리 클래스
factory class다. 이는 Thread-safe한 객체이므로 데이터가 의도하지 않게 바뀌는 것을 염려하지 않고, 여러 클래
스에서사용해도 된다. Session 인스턴스가 생성될 때 매핑 정보도 함께 전달하므로 컴파
일된 형태로 모든 매핑 데이터를 가진다
SessionFactory
캐시의 두 번째 수준을 관리한다. 이는 애플리케이션을 구성하는 모든 컴포넌트에도 해당된다. 전역global 캐시는
데이터베이스에서 이미 가져온동일한 결과를 여러 애플리케이션에서 요청하는 경우 사용된다.
SessionFactory가 데이터베이스 연결을 위한 열쇠 꾸러미를 가지고 있다면,
Session은 데이터베이스 접속과 데이터 이동이 이루어지는 열쇠 자체다. Session은 싱글 스레드single-threaded
객체이므로 여러 컴퍼넌트에 같이 선언되어 사용해서는 안 된다. 하나의 작업 단위를 뜻한다. 팩토리에서 세션을
가져오려면 factory.getCurrentSession() 메소드를 사용한다. 세션 객체를 얻으면 한 개의 트랜잭션
안에서 데이터베이스 작업을 수행한다. 세션과 트랜잭션은 밀접한 관련이 있다. 다음은 세션과 트랜잭션의 생애
주기를 보여주고 있다.
Hibernate-provided BasicTypes
Table 1. Standard BasicTypes
Hibernate type
(org.hibernate.type package)
JDBC type Java type BasicTypeRegistry key(s)
StringType VARCHAR java.lang.String string, java.lang.String
MaterializedClob CLOB java.lang.String materialized_clob
TextType LONGVARCHAR java.lang.String text
CharacterType CHAR
char,
java.lang.Character
char, java.lang.Character
BooleanType BIT
boolean,
java.lang.Boolean
boolean, java.lang.Boolean
NumericBooleanType
INTEGER, 0 is false,
1 is true
boolean,
java.lang.Boolean
numeric_boolean
YesNoType
CHAR, 'N'/'n' is
false, 'Y'/'y' is
true. The uppercase
value is written to
the database.
boolean,
java.lang.Boolean
yes_no
TrueFalseType
CHAR, 'F'/'f' is
false, 'T'/'t' is
true. The uppercase
value is written to
the database.
boolean,
java.lang.Boolean
true_false
ByteType TINYINT byte, java.lang.Byte byte, java.lang.Byte
ShortType SMALLINT short, java.lang.Short short, java.lang.Short
IntegerTypes INTEGER int, java.lang.Integer int, java.lang.Integer
LongType BIGINT long, java.lang.Long long, java.lang.Long
FloatType FLOAT float, java.lang.Float float, java.lang.Float
DoubleType DOUBLE
double,
java.lang.Double
double, java.lang.Double
BigIntegerType NUMERIC java.math.BigInteger
big_integer,
java.math.BigInteger
BigDecimalType NUMERIC java.math.BigDecimal
big_decimal,
java.math.bigDecimal
TimestampType TIMESTAMP java.sql.Timestamp
timestamp,
java.sql.Timestamp
TimeType TIME java.sql.Time time, java.sql.Time
DateType DATE java.sql.Date date, java.sql.Date
CalendarType TIMESTAMP java.util.Calendar
calendar,
java.util.Calendar
CalendarDateType DATE java.util.Calendar calendar_date
CalendarTimeType TIME java.util.Calendar calendar_time
CurrencyType java.util.Currency VARCHAR
currency,
java.util.Currency
LocaleType VARCHAR java.util.Locale locale, java.utility.locale
TimeZoneType
VARCHAR, using the
TimeZone ID
java.util.TimeZone
timezone,
java.util.TimeZone
UrlType VARCHAR java.net.URL url, java.net.URL
ClassType VARCHAR (class FQN) java.lang.Class class, java.lang.Class
BlobType BLOB java.sql.Blob blog, java.sql.Blob
ClobType CLOB java.sql.Clob clob, java.sql.Clob
BinaryType VARBINARY byte[] binary, byte[]
Table 1. Standard BasicTypes
Hibernate type
(org.hibernate.type package)
JDBC type Java type BasicTypeRegistry key(s)
MaterializedBlobType BLOB byte[] materized_blob
ImageType LONGVARBINARY byte[] image
WrapperBinaryType VARBINARY java.lang.Byte[]
wrapper-binary, Byte[],
java.lang.Byte[]
CharArrayType VARCHAR char[] characters, char[]
CharacterArrayType VARCHAR java.lang.Character[]
wrapper-characters,
Character[],
java.lang.Character[]
UUIDBinaryType BINARY java.util.UUID uuid-binary, java.util.UUID
UUIDCharType
CHAR, can also read
VARCHAR
java.util.UUID uuid-char
PostgresUUIDType
PostgreSQL UUID,
through Types#OTHER,
which complies to
the PostgreSQL JDBC
driver definition
java.util.UUID pg-uuid
SerializableType VARBINARY
implementors of
java.lang.Serializable
Unlike the other value
types, multiple instances
of this type are
registered. It is
registered once under
java.io.Serializable, and
registered under the
specific
java.io.Serializable
implementation class names.
StringNVarcharType NVARCHAR java.lang.String nstring
NTextType LONGNVARCHAR java.lang.String ntext
NClobType NCLOB java.sql.NClob nclob, java.sql.NClob
MaterializedNClobType NCLOB java.lang.String materialized_nclob
PrimitiveCharacterArrayNClobType NCHAR char[] N/A
CharacterNCharType NCHAR java.lang.Character ncharacter
CharacterArrayNClobType NCLOB java.lang.Character[] N/A
Table 2. Java 8 BasicTypes
Hibernate type (org.hibernate.type
package)
JDBC type Java type BasicTypeRegistry key(s)
DurationType BIGINT java.time.Duration Duration, java.time.Duration
InstantType TIMESTAMP java.time.Instant Instant, java.time.Instant
LocalDateTimeType TIMESTAMP java.time.LocalDateTime
LocalDateTime,
java.time.LocalDateTime
LocalDateType DATE java.time.LocalDate LocalDate, java.time.LocalDate
LocalTimeType TIME java.time.LocalTime LocalTime, java.time.LocalTime
OffsetDateTimeType TIMESTAMP java.time.OffsetDateTime
OffsetDateTime,
java.time.OffsetDateTime
OffsetTimeType TIME java.time.OffsetTime OffsetTime, java.time.OffsetTime
OffsetTimeType TIMESTAMP java.time.ZonedDateTime
ZonedDateTime,
java.time.ZonedDateTime
Table 3. Hibernate Spatial BasicTypes
Hibernate type
(org.hibernate.spatial
package)
JDBC type Java type BasicTypeRegistry key(s)
JTSGeometryType
depends on
the
dialect
com.vividsolutions.jts.geom.Geometry
jts_geometry, or the classname
of Geometry or any of its
subclasses
GeolatteGeometryType
depends on
the
dialect
org.geolatte.geom.Geometry
geolatte_geometry, or the
classname of Geometry or any
of its subclasses
Entity
클래스를 영속화하려면 먼저 엔티티로 정의해야 하는데, @Entity 어노테이션으로 정의할 수 있다
또한 xml으로도 지정가능합니다.
@Entity
package com.khh.hibernate.c1.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.Entity;
import javax.persistence.Id;
@Data
@Entity
public class UserNormal {
@Id
Integer seq;
String name;
String address;
Integer age;
}
drop table UserNormal if exists
create table UserNormal (
seq integer not null,
address varchar(255),
age integer,
name varchar(255),
primary key (seq)
)
기본값으로 Entity 클래스 이름이 테이블 이름으로 적용되고 필드 이름이 컬럼 이름으로 생성된다.
Table name, Column name 을 직접 지정할수 있다.
package com.khh.hibernate.c1.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Entity
@Table(name = "USER")
public class UserNaming {
@Id
@Column(name = "JUMIN_NUMBER")
Integer seq;
@Column(name = "USER_NAME")
String name;
@Column(name = "USER_ADDR")
String address;
@Column(name = "USER_AGE")
Integer age;
}
drop table UserNormal if exists
create table USER (
JUMIN_NUMBER integer not null,
USER_ADDR varchar(255),
USER_AGE integer,
USER_NAME varchar(255),
primary key (JUMIN_NUMBER)
)
식별자
각 객체는 유일한 식별자를 가지고 데이터베이스에 영속화되어야 한다. 이때 식별자를 자동으로 생성하는
다양한 방법을 활용할 수 있다.
@GeneratedValue
어노테이션을 추가하면 요구 사항에 맞춘 다른 생성 방법을 설정할 수 있다.
@GenerateValue 어노테이션은 strategy와 generator 두 가지 속성이 있는데,
strategy 속성은 사용할 식별자 생성 타입을 가리키고 generator속성은 식별자를 생성할 메소드를 정의한다.
다음 코드는 ID 생성을 위한 IDENTITY 방법을 보여준다
기본값 : GenerationType.AUTO
GenerationType.AUTO 기본 방법으로 다른 데이터베이스 간에도 이용할 수 있다. 하이버네이트에서는
데이터베이스를 기반으로 적절한 ID를 선택한다.
GenerationType.IDENTITY 이 설정은 몇몇 데이터베이스에서 제공하는 identity 함수를 기반으로 한다. 데
이터베이스에서 고유한 식별자를 제공하는 역할을 한다.
GenerationType.SEQUENCE 몇몇 데이터베이스에서는 연속된 숫자에 관한 메커니즘을 제공하는데, 하이버
네이트에서는 일련번호를 사용한다.
GenerationType.TABLE 다른 테이블의 고유한 컬럼 값에서 기본키를 생성하는데, 이 경우 TABLE 생성자
를 사용한다. 시퀀스seqeunce 방법에서는 strategy와 generator 속성을 모두 정의해
야 한다.
GenerationType.AUTO
@Data
@Entity
public class UserGen_AUTO {
@Id
@GeneratedValue(strategy =
GenerationType.AUTO)
Integer seq;
String name;
String address;
Integer age;
}
create sequence hibernate_sequence start with 1 increment
by 1
create table UserGen_AUTO (
seq integer not null,
address varchar(255),
age integer,
name varchar(255),
primary key (seq)
)
자동으로 hibernate_sequence를 생성한다.
▼실행
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
UserGen_AUTO user = new UserGen_AUTO();
user.setName("name");
user.setAddress("addr");
user.setAge(100);
session.save(user);
session.getTransaction().commit();
call next value for hibernate_sequence
insert
into
UserGen_AUTO
(address, age, name, seq)
values
(?, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [addr]
binding parameter [2] as [INTEGER] - [100]
binding parameter [3] as [VARCHAR] - [name]
binding parameter [4] as [INTEGER] - [1]
GenerationType.IDENTITY
@Data
@Entity
public class UserGen_IDENTITY {
@Id
@GeneratedValue(strategy =
GenerationType.IDENTITY)
Integer seq;
String name;
String address;
Integer age;
}
create table UserGen_IDENTITY (
seq integer generated by default as identity,
address varchar(255),
age integer,
name varchar(255),
primary key (seq)
) )
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserGen_IDENTITY user = new UserGen_IDENTITY();
user.setName("name");
user.setAddress("addr");
user.setAge(100);
session.save(user);
session.getTransaction().commit();
insert
into
UserGen_IDENTITY
(seq, address, age, name)
values
(null, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [addr]
binding parameter [2] as [INTEGER] - [100]
binding parameter [3] as [VARCHAR] - [name]
GenerationType.SEQUENCE
@Data
@Entity
@SequenceGenerator(name = UserGen_SEQUENCE.SEQUENCE_GEN_NAME,
sequenceName = "EMP_SEQ_GEN")
public class UserGen_SEQUENCE {
public static final String SEQUENCE_GEN_NAME = "empSeqGen";
@Id
@GeneratedValue (strategy = GenerationType.SEQUENCE,
generator = SEQUENCE_GEN_NAME)
Integer seq;
String name;
String address;
Integer age;
}
create sequence EMP_SEQ_GEN start with 1
increment by 50
create table UserGen_SEQUENCE (
seq integer not null,
address varchar(255),
age integer,
name varchar(255),
primary key (seq)
)
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserGen_SEQUENCE user = new UserGen_SEQUENCE();
user.setName("name");
user.setAddress("addr");
user.setAge(100);
session.save(user);
session.getTransaction().commit();
call next value for EMP_SEQ_GEN
insert
into
UserGen_SEQUENCE
(address, age, name, seq)
values
(?, ?, ?, ?)
15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [addr]
15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - [100]
15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [name]
15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - [1]
GenerationType.TABLE
@Data
@Entity
@TableGenerator(name = UserGen_TABLE.TABLE_GEN_NAME, table =
"USER_ID_TABLE")
public class UserGen_TABLE {
public static final String TABLE_GEN_NAME =
"empTableGen";
@Id
@GeneratedValue (strategy = GenerationType.TABLE,
generator = TABLE_GEN_NAME)
Integer seq;
String name;
String address;
Integer age;
}
create table USER_ID_TABLE (
sequence_name varchar(255) not null,
next_val bigint,
primary key (sequence_name)
)
create table UserGen_TABLE (
seq integer not null,
address varchar(255),
age integer,
name varchar(255),
primary key (seq)
)
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserGen_TABLE user = new UserGen_TABLE();
user.setName("name");
session.save(user);
session.getTransaction().commit();
select
tbl.next_val
from
USER_ID_TABLE tbl
where
tbl.sequence_name=? for update
insert
into
USER_ID_TABLE
(sequence_name, next_val)
values
(?,?)
update
USER_ID_TABLE
set
next_val=?
where
next_val=?
and sequence_name=?
select
tbl.next_val
from
USER_ID_TABLE tbl
where
tbl.sequence_name=? for update
update
USER_ID_TABLE
set
next_val=?
where
next_val=?
and sequence_name=?
insert
into
UserGen_TABLE
(address, age, name, seq)
values
(?, ?, ?, ?)
13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [null]
13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - [null]
13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [name]
13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - [1]
복합 식별자 ID (KEY)
복합 아이디composite-id 식별자 설정과 관련된 세 가지 방법
1. @Embededdable
2. @EmbeddedId
3. @IdClass
ID 값으로 사용되는 클래스에서 구현해야될것들
1. Default Constructor()
2. hashCode()
3. equals()
4. implements Serializable
tip : Lombok lib 사용한다면 위 내용을 자동으로 생성해준다.( https://projectlombok.org/ )
키선언 첫번째 방법 (@Embeddable)
@Data
@Embeddable
public class UserPK_Embededdable implements
Serializable{
String name;
Integer number;
}
@Data
@Entity
public class UserInfo{
@Id
UserPK_Embededdable id;
String address;
Integer age;
}
create table UserInfo (
name varchar(255) not null,
number integer not n
ull,
address varchar(255),
age integer,
primary key (name, number)
)
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
UserPK_Embededdable pk = new
UserPK_Embededdable();
pk.setName("name");
pk.setNumber(128);
UserInfo_Embededdable user = new
UserInfo_Embededdable();
user.setId(pk);
user.setAddress("korea");
user.setAge(53);
session.save(user);
session.getTransaction().commit();
insert
into
UserInfo_Embededdable
(address, age, name, number)
values
(?, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [korea]
binding parameter [2] as [INTEGER] - [53]
binding parameter [3] as [VARCHAR] - [name]
binding parameter [4] as [INTEGER] - [128]
//select
UserPK_Embededdable userPK = new
UserPK_Embededdable();
UserPK_IdClass();
userPK.setName("name");
userPK.setNumber(128);
UserInfo_Embededdable userBydb =
session.get(UserInfo_Embededdable.class,userPK
);
log.info("get::"+userBydb.toString());
select
userinfo_e0_.name as name1_1_0_,
userinfo_e0_.number as number2_1_0_,
userinfo_e0_.address as address3_1_0_,
userinfo_e0_.age as age4_1_0_
from
UserInfo_Embededdable userinfo_e0_
where
userinfo_e0_.name=?
and userinfo_e0_.number=?
binding parameter [1] as [VARCHAR] - [name]
binding parameter [2] as [INTEGER] - [128]
get::UserInfo_Embededdable(id=UserPK_Embededdable(name=name, number=128), address=korea, age=53)
tip : @Embeddable 을 선언하지 않고 @Id 로 사용한다면 해당클래스의 toString()값이 들어간다
키선언 두번째 방법 (@EmbeddedId)
@Data
public class UserPK_EmbeddedId implements
Serializable{
String name;
Integer number;
}
@Data
@Entity
public class UserInfo_EmbeddedId {
@EmbeddedId
UserPK_Embededdable id;
String address;
Integer age;
}
create table UserInfo_EmbeddedId (
name varchar(255) not null,
number integer not null,
address varchar(255),
age integer,
primary key (name, number)
)
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
UserPK_EmbeddedId pk = new
UserPK_EmbeddedId();
pk.setName("name");
pk.setNumber(128);
UserInfo_EmbeddedId user = new
UserInfo_EmbeddedId();
user.setId(pk);
user.setAddress("korea");
user.setAge(53);
session.save(user);
session.getTransaction().commit();
insert
into
UserInfo_EmbeddedId
(address, age, name, number)
values
(?, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [korea]
binding parameter [2] as [INTEGER] - [53]
binding parameter [3] as [VARCHAR] - [name]
binding parameter [4] as [INTEGER] - [128]
//select
UserPK_EmbeddedId userPK = new
UserPK_EmbeddedId();
userPK.setName("name");
userPK.setNumber(128);
UserInfo_EmbeddedId userBydb =
session.get(UserInfo_EmbeddedId.class,userPK)
;
log.info("get::"+userBydb.toString());
select
userinfo_e0_.name as name1_0_0_,
userinfo_e0_.number as number2_0_0_,
userinfo_e0_.address as address3_0_0_,
userinfo_e0_.age as age4_0_0_
from
UserInfo_EmbeddedId userinfo_e0_
where
userinfo_e0_.name=?
and userinfo_e0_.number=?
binding parameter [1] as [VARCHAR] - [name]
binding parameter [2] as [INTEGER] - [128]
get::UserInfo_EmbeddedId(id=UserPK_EmbeddedId(name=name, number=128), address=korea, age=53)
키선언 세번째 방법 (@IdClass)
@Data
public class UserPK_IdClass implements
Serializable{
String name;
Integer number;
}
@Data
@Entity
@IdClass(UserPK_IdClass.class)
public class UserInfo_IdClass {
@Id
String name;
@Id
Integer number;
String address;
Integer age;
}
create table UserInfo_IdClass (
name varchar(255) not null,
number integer not null,
address varchar(255),
age integer,
primary key (name, number)
)
▼실행
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
UserInfo_IdClass pk = new
UserInfo_IdClass();
pk.setName("name");
pk.setNumber(128);
UserInfo_IdClass user = pk;
user.setAddress("korea");
user.setAge(53);
session.save(user);
session.getTransaction().commit();
insert
into
UserInfo_IdClass
(address, age, name, number)
values
(?, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [korea]
binding parameter [2] as [INTEGER] - [53]
binding parameter [3] as [VARCHAR] - [name]
binding parameter [4] as [INTEGER] - [128]
UserPK_IdClass userPK = new
UserPK_IdClass();
userPK.setName("name");
userPK.setNumber(128);
UserInfo_IdClass userBydb =
session.get(UserInfo_IdClass.class,userPK);
log.info("get::"+userBydb.toString());
select
userinfo_i0_.name as name1_2_0_,
userinfo_i0_.number as number2_2_0_,
userinfo_i0_.address as address3_2_0_,
userinfo_i0_.age as age4_2_0_
from
UserInfo_IdClass userinfo_i0_
where
userinfo_i0_.name=?
and userinfo_i0_.number=?
binding parameter [1] as [VARCHAR] - [name]
binding parameter [2] as [INTEGER] - [128]
get::UserInfo_IdClass(name=name, number=128, address=korea, age=53)
Join 조인
연관 관계
객체 영속화 세상에서는 연관 관계 assocation 와 관계 relationship 에 대한 이해는 필수입니다.
연관 관계에서 반드시 기억할 두 가지는 다중성 multiplicity 과 방향성 directionality 입니다
다중성
일대일 1:1 한 테이블에서 각 레코드는 반드시 다른 테이블 의
레코드 한 개와 관계가 있다. 반대의 경우도 마찬가지다.
다른 테이블의 레코드는 0 일 수도 있다.
자동차 한 대는 오직 한 개의
엔진만 가진다.
일대다
또는
다대일
1:N, N:1
한 테이블에서 각 레코드는 다른 테이블의 0 개
또는 그 이상의 레코드와 관계가 있다.
영화 한 편은 많은
배우를 가진다(일대다)
배우 한 명은 여러 작품에서
연기할 수 있다 (다대일).
N:N 양쪽 테이블 모두 각 레코드가 다른 쪽 테이블의
0 개 또는 그 이상의 레코드와 관계가 있다.
방향성
Car 와 Engine 의 관계에서 Car 의 속성을 질의해서 Engine 을 찾아낼수있습니다. car -> engin
Car 클래스와 Owner 클래스의 경우 주어진 Car 객체로 자동차의 주인이 누구인지 알 수 있으며,
Owner 객체로 차주의 자동차가 무엇인지 알 수 있습니다
양방향성 연관 관계를 유지할 수 있도록 Owner 객체에 Car 에 대한 참조를 제공하 고, Car 객체에는
Owner 에 대한 참조를 제공하고 있습니다.
조인전에 먼저 알아야될 Cascade
부모 객체와 자식 객체의 종속성 설정 : 하이버네이트에서는 "부모"객체가 실행되면 "자식" 혹은 "의존" 객체까지
전이되는 연산을 cascade 어트리뷰트로 처리할 수 있다. 이 기능은 모든 종류의 컬렉션과 연관에 적용된다.
타입 행위 언제
CascadeType.DETACH 엔티티가 Persistence Context 에서 제거되면
(엔티티가 분리 될 것입니다)이 작업은 관계에
반영됩니다.
Finished Persistence Context
또는 entityManager.detach ()
entityManager.clear ()
CascadeType.MERGE 엔티티에 업데이트 된 데이터가 있으면이
작업이 관계에 반영됩니다
엔티티가 갱신되고 트랜잭션이
완료되거나, entityManager.merge ()
CascadeType.PERSIST 새로운 엔티티가 데이터베이스에 유지되면이
조치가 관계에 반영됩니다.
트랜잭션이 끝나거나,
entityManager.persist ()
CascadeType.REFRESH 엔티티가 데이터베이스와 동기화 된 데이터를
가질 때이 조치가 반영됩니다
entityManager.refresh ()
CascadeType.REMOVE 엔터티가 데이터베이스에서 삭제되면 entityManager.remove ()
행동이 관계에 반영 될 것입니다.
CascadeType.ALL 위의 조치 중 하나가 JPA 또는 명령에 의해
호출 될 때,이 조치는 관계에 반영됩니다.
위에 설명 된 명령이나 행동.
쉽게 말하기 : 객체 상태 전이 타입 (보통 ALL 을 사용한다)
@OneToOne
1. FK 지정
@Data
@Entity
public class User_OneToOne {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String name;
String password;
@OneToOne(cascade = CascadeType.ALL)
UserInfo info;
}
@Data
@Entity
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Integer seq;
String addr;
double weight;
double height;
}
create table UserInfo (
seq integer not null,
addr varchar(255),
height double not null,
weight double not null,
primary key (seq)
)
create table User_OneToOne (
seq integer generated by default as
identity,
name varchar(255),
password varchar(255),
info_seq integer,
primary key (seq)
)
alter table User_OneToOne
add constraint
FKmn264qa7ngjx89j3u9a2l7ql2
foreign key (info_seq)
references UserInfo
집고 넘어가기 : 자동으로 User_OneToOne Table(부모테이블)에 INFO_SEQ컬럼 생기고 FK를건다
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserInfo info = new UserInfo();
info.setAddr("addr");
info.setHeight(180);
info.setWeight(70);
User_OneToOne user = new User_OneToOne();
user.setName("name");
user.setPassword("pwd");
user.setInfo(info);
session.save(user);
session.getTransaction().commit();
insert
into
UserInfo
(addr, height, weight, seq)
values
(?, ?, ?, ?)
binding parameter [1] as [VARCHAR] - [addr]
binding parameter [2] as [DOUBLE] - [180.0]
binding parameter [3] as [DOUBLE] - [70.0]
binding parameter [4] as [INTEGER] - [1]
insert
into
User_OneToOne
(seq, info_seq, name, password)
values
(null, ?, ?, ?)
binding parameter [1] as [INTEGER] - [1]
binding parameter [2] as [VARCHAR] - [name]
binding parameter [3] as [VARCHAR] - [pwd]
1. PK 를 FK 지정 (아주 중요함)
@Data
@Entity
@SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 100, allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = SEQ_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName="SEQ")
// @PrimaryKeyJoinColumn(name = "USER_SEQ", referencedColumnName="SEQ")
UserInfo info;
}
@Data
@Entity
@GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value =
"user"))
public class UserInfo {
@Id
@Column(name = "SEQ")
@GeneratedValue(generator = "generator")
Integer seq;
String addr;
double weight;
double height;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "info")
User user;
}
create table User (
USER_SEQ integer not null,
name varchar(255),
password varchar(255),
primary key (USER_SEQ)
)
create table UserInfo (
SEQ integer not null,
addr varchar(255),
height double not null,
weight double not null,
primary key (SEQ)
)
▼ 실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
call next value for SEQ_USER
UserInfo info = new UserInfo();
info.setAddr("addr");
info.setHeight(180);
info.setWeight(70);
User user = new User();
user.setName("name");
user.setPassword("pwd");
//서로의 관계를 맺어준다.
user.setInfo(info);
info.setUser(user);
session.save(user);
session.flush();
session.clear();
UserInfo userInfoBydb = session.get(UserInfo.class,100);
if(null!=userInfoBydb && null!=userInfoBydb.getUser())
log.debug("get(DB) Entity --->
"+userInfoBydb.getUser().getName());
session.getTransaction().commit();
Sequence value obtained: 100
insert
into
User
(name, password, USER_SEQ)
values
(?, ?, ?)
[1] as [VARCHAR] - [name]
[2] as [VARCHAR] - [pwd]
[3] as [INTEGER] - [100]
insert
into
UserInfo
(addr, height, weight, SEQ)
values
(?, ?, ?, ?)
[1] as [VARCHAR] - [addr]
[2] as [DOUBLE] - [180.0]
[3] as [DOUBLE] - [70.0]
[4] as [INTEGER] - [100]
select
userinfo0_.SEQ as SEQ1_1_0_,
userinfo0_.addr as addr2_1_0_,
userinfo0_.height as
height3_1_0_,
userinfo0_.weight as
weight4_1_0_,
user1_.USER_SEQ as
USER_SEQ1_0_1_,
user1_.name as name2_0_1_,
user1_.password as
password3_0_1_
from
UserInfo userinfo0_
left outer join
User user1_
on
userinfo0_.SEQ=user1_.USER_SEQ
where
userinfo0_.SEQ=?
[1] as [INTEGER] - [100]
get(DB) Entity ---> name
자동으로 부모 테이블의 PK값을 가지고 자신의 FK쪽 값으로 사용한다
@GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value =
"user"))
....
@GeneratedValue(generator = "generator")
Integer seq;
value값은 부모Entity객체 필드명
집고 넘어가기 : @JoinColumn(name = "USER_SEQ", referencedColumnName="SEQ")
name = 자기 자신 테이블의 FK대상 컬럼명
referencedColumnName = 자식테이블 컬럼명 (안적으면 자동으로 자식테이블 ID 로 해준다.)
또한 PrimaryKeyJoinColumn 을 쓸수도 있다
@PrimaryKeyJoinColumn(name = "USER_SEQ", referencedColumnName="SEQ")
방향성을 갖자
소유자 찾기
@Data
@Entity
@SequenceGenerator(name = User.SEQ_NAME, sequenceName =
User.SEQ_NAME, initialValue = 100, allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy =
GenerationType.SEQUENCE,generator = SEQ_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToOne(cascade = CascadeType.ALL)
UserInfo info;
}
@Data
@Entity
@GenericGenerator(name = "generator", strategy =
"foreign", parameters = @Parameter(name = "property",
value = "user"))
public class UserInfo {
@Id
@Column(name = "SEQ")
@GeneratedValue(generator = "generator")
Integer seq;
String addr;
double weight;
double height;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "info")
User user;
}
UserInfo Entity (자식테이블) 에서 부모Entity에서 선언된 자식Entity의 필드명을 적으면된다
@OneToOne(mappedBy = “code feildName”)
집고 넘어가기 : insert 했을때 Entity 는 관계설정이 되어있지 않아 로그가 찍히지 않는다 하지만 다시
SELECT 해온 Entity 는 방향성 설정이 되어 넘어온다 여기서 알수 있는것은 insert 했을때 사용했던 Entity 와
get 해온 Entity 는 다르다는것을 알수 있다.
@OneToMany, @ManyToOne
1:N, N:1, N:N 컬렉션 영속화 (List, Set, Map, Array[])
List
▼ OneToMany 조인하기 맵핑 테이블 만들어 조인하기
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
List<Auth> auths = null;
}
@Data
@Entity
@SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName = Auth.SEQUENCE_NAME, initialValue = 100)
public class Auth implements Serializable {
public static final String SEQUENCE_NAME = "AUTH_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
Integer seq;
String auth;
Date expiry;
}
create table Auth (
seq integer not null,
auth varchar(255),
expiry timestamp,
primary key (seq)
)
create table User (
seq integer generated by default as identity,
name varchar(255),
password varchar(255),
primary key (seq)
)
-- 새로운 맵핑 테이블이 생긴다
create table User_Auth (
User_seq integer not null,
auths_seq integer not null
)
alter table User_Auth
add constraint UK_n1upof81pc3xys7s3xrovghof unique (auths_seq)
alter table User_Auth
add constraint FKmnns1wp8mo27pxkbbwjl8nuun
foreign key (auths_seq)
references Auth
alter table User_Auth
add constraint FKn1dj4srm089e409sq8vkhogug
foreign key (User_seq)
references User
▼맵핑 테이블 이름바꾸기
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "USER_AUTH_MAPPING")
List<Auth> auths = null;
}
▼실행
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
Auth auth1 = new Auth();
auth1.setAuth("ROLE_ADMIB");
auth1.setExpiry(new Date());
Auth auth2 = new Auth();
auth2.setAuth("ROLE_USER");
auth2.setExpiry(new Date());
User user = new User();
user.setAuths(Arrays.asList(auth1,auth2));
user.setName("name");
user.setPassword("pwd");
session.save(user);
session.getTransaction().commit();
insert
into
Auth
(auth, expiry, seq)
values
(?, ?, ?)
binding parameter [1] as [VARCHAR] - [ROLE_ADMIB]
binding parameter [2] as [TIMESTAMP] - [Sat Dec 17
14:27:21 KST 2016]
binding parameter [3] as [INTEGER] - [100]
insert
into
Auth
(auth, expiry, seq)
values
(?, ?, ?)
binding parameter [1] as [VARCHAR] - [ROLE_USER]
binding parameter [2] as [TIMESTAMP] - [Sat Dec 17
14:27:21 KST 2016]
binding parameter [3] as [INTEGER] - [101]
insert
into
User_Auth
(User_seq, auths_seq)
values
(?, ?)
binding parameter [1] as [INTEGER] - [1]
binding parameter [2] as [INTEGER] - [100]
insert
into
User_Auth
(User_seq, auths_seq)
values
(?, ?)
binding parameter [1] as [INTEGER] - [1]
binding parameter [2] as [INTEGER] - [101]
Array
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize
= 1)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ")
@OrderColumn
private AuthComposite[] auths;
}
user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).toArray(AuthComposite[]::new));
집고 넘어가기 : Array사용할때에는 @OrderColumn으로 선언해야된다.
@OrderColumn은 Index값이 들어가도록 하는것입니다.
Set
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize
= 1)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ")
private Set<AuthComposite> auths;
}
user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).collect(Collectors.toSet()));
Map
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize
= 1)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ")
private Map<String,AuthComposite> auths;
}
user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).
collect(Collectors.toMap(c-> c.getAuth(),c->c)));
▼바로 OneToMany 조인하기(맵핑테이블 없음) (자식테이블 인조식별자 + 유니크 인덱스)
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn
List<Auth> auths = null;
}
@Data
@Entity
@SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName =
Auth.SEQUENCE_NAME, initialValue = 100)
public class Auth implements Serializable {
public static final String SEQUENCE_NAME = "AUTH_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String auth;
Date expiry;
}
▼자식테이블 FK 컬럼 명바꾸기
Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ")
List<Auth> auths = null;
}
집고 넘어가기 : OneToOne에서는 자기자신 컬럼을 가르켰지만 OneToMany에서는 자식컬럼을 말한다.
▼PK 를 FK 지정 (맵핑 테이블 없음)
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME,
sequenceName = User.SEQUENCE_NAME, initialValue =
100)
public class User {
public static final String SEQUENCE_NAME =
"USER_SEQ";
@Id
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ",
referencedColumnName = "SEQ")
List<Auth> auths = null;
}
@Data
@Entity
@SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName =
Auth.SEQUENCE_NAME, initialValue = 100)
public class Auth implements Serializable {
public static final String SEQUENCE_NAME = "AUTH_SEQ";
@Id
@Column(name = "USER_SEQ")
Integer seq;
@Id
String auth;
Date expiry;
}
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
Auth auth1 = new Auth();
auth1.setAuth("ROLE_ADMIB");
auth1.setExpiry(new Date());
Auth auth2 = new Auth();
auth2.setAuth("ROLE_USER");
auth2.setExpiry(new Date());
User user = new User();
user.setSeq(50);
user.setAuths(Stream.of(auth1,auth2).peek(it-
>it.setSeq(user.getSeq())).collect(Collectors.toList()));
user.setName("name");
user.setPassword("pwd");
session.save(user);
session.getTransaction().commit();
집고 넘어가기 : (PK를 FK지정) (맵핑 테이블 없음) 는 좀 문제가 있다. 수동으로 KEY값을 맵핑시켜줘다.
자동으로 맵핑할수 있는 방법도 있다. (아래에서 알아보자)
@GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value
= "user"))
http://howtodoinjava.com/hibernate/hibernate-one-to-many-mapping-using-annotations/
https://en.wikibooks.org/wiki/Java_Persistence/OneToMany
http://www.beingjavaguys.com/2013/09/hibernate-one-to-many-mapping.html
http://levelup.lishman.com/hibernate/associations/one-to-many.php
▼PK 를 FK 지정 (맵핑 테이블 없음) GeneratedValue 값으로 KEY 값 처리시 문제.
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ")
private List<AuthComposite> auths;
}
@Data
@Entity
@SequenceGenerator(name = AuthComposite.SEQUENCE_NAME, sequenceName = AuthComposite.SEQUENCE_NAME, initialValue = 50)
public class AuthComposite implements Serializable{
public static final String SEQUENCE_NAME = "AUTHCOMPOSITE_SEQ";
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Id
@Column(name = "USER_SEQ",nullable = false)
Integer seq;
@Id
@Column(nullable = false)
String auth;
Date expiry;
}
insert
into
User
(name, password, USER_SEQ)
values
(?, ?, ?)
12:08:37 TRACE : binding parameter [1] as [VARCHAR] - [name]
12:08:37 TRACE : binding parameter [2] as [VARCHAR] - [pwd]
12:08:37 TRACE : binding parameter [3] as [INTEGER] - [100]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
12:08:37 TRACE : binding parameter [1] as [TIMESTAMP] - [Sun Dec 18 12:08:36 KST 2016]
12:08:37 TRACE : binding parameter [2] as [INTEGER] - [50]
12:08:37 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_ADMIN]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
12:08:37 TRACE : binding parameter [1] as [TIMESTAMP] - [Sun Dec 18 12:08:36 KST 2016]
12:08:37 TRACE : binding parameter [2] as [INTEGER] - [51]
12:08:37 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_USER]
ERROR : HHH000315: Exception executing batch [org.h2.jdbc.JdbcBatchUpdateException: Referential integrity
constraint violation: "FKOU1XVFC0SWVOFR46KFD8KHY1N: PUBLIC.AUTHCOMPOSITE FOREIGN KEY(USER_SEQ) REFERENCES
PUBLIC.USER(USER_SEQ) (51)"; SQL statement:
AuthComposite authc1 = new AuthComposite();
authc1.setAuth("ROLE_ADMIN");
authc1.setExpiry(new Date());
AuthComposite authc2 = new AuthComposite();
authc2.setAuth("ROLE_USER");
authc2.setExpiry(new Date());
User user = new User();
user.setAuths(Stream.of(authc1,authc2).collect(Collectors.toList()));
user.setName("name");
user.setPassword("pwd");
log.debug("---->"+user.getSeq());
session.save(user);
집고 넘어가기 : 부모Entity와 자식Entity에 관계가 맺어있지만 각각클래스의 ID값에 GeneratedValue 설정했기떄문
에 각각 시퀀스 값으로 들어간다 여기서 FK제약조건이 걸려 오류가 난것이다.
save 할때 어떻게 해야될까?
1. 이렇게 해보자
부모Entity는 ID는 GeneratedValue 자식Entity은 GeneratedValue사용하지 않고 자동으로 넘어가도록 기대해보자
@Data
@Entity
public class AuthComposite implements Serializable{
@Id
@Column(name = "USER_SEQ",nullable = false)
Integer seq;
@Id
@Column(nullable = false)
String auth;
Date expiry;
}
insert
into
User
(name, password, USER_SEQ)
values
(?, ?, ?)
parameter [1] as [VARCHAR] - [name]
parameter [2] as [VARCHAR] - [pwd]
parameter [3] as [INTEGER] - [100]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
parameter [1] as [TIMESTAMP] - [Sun Dec 18
13:10:36 KST 2016]
parameter [2] as [INTEGER] - [null]
parameter [3] as [VARCHAR] - [ROLE_ADMIN]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
parameter [1] as [TIMESTAMP] - [Sun Dec 18
13:10:36 KST 2016]
parameter [2] as [INTEGER] - [null]
parameter [3] as [VARCHAR] - [ROLE_USER]
기대는 했지만 자동으로 넘어가지 않는다. 그저 NULL값이 들어간다 정직하다.. 오류난다
2. 이렇게 해보자 (나눠서 save하자)
부모Entity만 save 선행후~ 부모Entity에서 추출된 ID (SEQUENCE) 값을 가지고
자식Entity ID값으로 지정 그뒤 자식Entity를 save해보자
AuthComposite authc1 = new AuthComposite();
authc1.setAuth("ROLE_ADMIN");
authc1.setExpiry(new Date());
AuthComposite authc2 = new AuthComposite();
authc2.setAuth("ROLE_USER");
authc2.setExpiry(new Date());
User user = new User();
user.setAuths(Stream.of(authc1,authc2).collect(Collectors.toList()));
user.setName("name");
user.setPassword("pwd");
log.debug("---->"+user.getSeq());
session.save(user);
log.debug("---->"+user.getSeq());
user.getAuths().forEach(it->it.setSeq(user.getSeq()));
---->null
call next value for USER_SEQ
Sequence value obtained: 100
HHH000387: ResultSet's statement was not registered
Generated identifier: 100, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
---->100
insert
into
User
(name, password, USER_SEQ)
values
(?, ?, ?)
parameter [1] as [VARCHAR] - [name]
parameter [2] as [VARCHAR] - [pwd]
parameter [3] as [INTEGER] - [100]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:21:49 KST 2016]
parameter [2] as [INTEGER] - [100]
parameter [3] as [VARCHAR] - [ROLE_ADMIN]
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:21:49 KST 2016]
parameter [2] as [INTEGER] - [100]
parameter [3] as [VARCHAR] - [ROLE_USER]
집고 넘어가기 1 : 부모Entity만 save후 자식Entity List를 넣은뒤 다시 save했다
여기서 알수 있듯 save 를 타게되면 바로 시퀀스 값을 가져와 바인딩 시켜주니 그것을 가지고 자식 셋팅후
commit을 하면 그때 가서 자식 Entity에 바인딩된 ID값으로 insert된다.
집고 넘어가기 2 : PK를 PK복합키로만 구성하는 방법과, 인조식별자+유니크 인덱스로
구성하는것은 아직도 많은 논쟁있다. 하지만 어느정도 대세는 인조식별자+유니크 를 두는것으로
많이 기울었습니다. http://okky.kr/article/257331
3. 이렇게 해보자 (자식Entity에 ID값을 부모Entity의 ID값으로 자동 save하자) 베스트!
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME,
sequenceName = User.SEQUENCE_NAME, initialValue = 100,
allocationSize = 1)
public class User {
public static final String SEQUENCE_NAME =
"USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ", referencedColumnName
= "USER_SEQ")
private List<AuthComposite> auths;
}
@Data
@Entity
@GenericGenerator(name = "generator", strategy = "foreign",
parameters = @Parameter(name = "property", value = "user"))
public class AuthComposite implements Serializable{
@Id
@Column(name = "USER_SEQ",nullable = false)
@GeneratedValue(generator = "generator")
Integer seq;
@Id
@Column(nullable = false)
String auth;
Date expiry;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="USER_SEQ",
nullable=false,updatable=false,insertable=false)
User user;
}
AuthComposite authc1 = new AuthComposite();
authc1.setAuth("ROLE_ADMIN");
authc1.setExpiry(new Date());
AuthComposite authc2 = new AuthComposite();
authc2.setAuth("ROLE_USER");
authc2.setExpiry(new Date());
User user = new User();
user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).collect(Collectors.toList()));
user.setName("name");
user.setPassword("pwd");
Serializable id = session.save(user);
insert
into
User
(name, password, USER_SEQ)
values
(?, ?, ?)
10:44:27 TRACE : binding parameter [1] as [VARCHAR] - [name]
10:44:27 TRACE : binding parameter [2] as [VARCHAR] - [pwd]
10:44:27 TRACE : binding parameter [3] as [INTEGER] - [100]
10:44:27 DEBUG : Executing batch size: 1
10:44:27 DEBUG :
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
10:44:27 TRACE : binding parameter [1] as [TIMESTAMP] - [Wed Dec 21 10:44:27 KST 2016]
10:44:27 TRACE : binding parameter [2] as [INTEGER] - [100]
10:44:27 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_ADMIN]
10:44:27 DEBUG : Reusing batch statement
10:44:27 DEBUG :
insert
into
AuthComposite
(expiry, USER_SEQ, auth)
values
(?, ?, ?)
10:44:27 TRACE : binding parameter [1] as [TIMESTAMP] - [Wed Dec 21 10:44:27 KST 2016]
10:44:27 TRACE : binding parameter [2] as [INTEGER] - [100]
10:44:27 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_USER]
객체간 관계만 맺어주면 (변수 값셋팅)
자동으로 부모Entity값을 가지고 자기자신ID값으로 사용하는걸 알수 있다.
https://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example-annotation/
http://www.codejava.net/frameworks/hibernate/hibernate-one-to-one-association-on-primary-key-annotations-
example
방향성 갖자
집고 넘어가기 1 : @MonyToOne 쪽에 @JoinColums 를 사용하여 자기자신 테이블 컬럼명을 지정할수도있다.
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="USER_SEQ", nullable=false,updatable=false,insertable=false)
User user;
조회하기
지연로딩 FetchType.LAZY (기본값)
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "USER_SEQ")
List<Auth> auths = null;
}
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
//////Fetch
User userBydb = session.get(User.class,100); //부모테이블만 조회
try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
//아래에서 사용할때 자식테이블 조회
userBydb.getAuths().forEach(it->log.info("get(DB)"+it.getUser().getName()));
session.getTransaction().commit();
18:04:14 DEBUG :
select
user0_.seq as seq1_1_0_,
user0_.name as name2_1_0_,
user0_.password as password3_1_0_
from
User user0_
where
user0_.seq=?
18:04:14 TRACE : binding parameter [1] as [INTEGER] - [100]
18:04:19 DEBUG :
select
auths0_.USER_SEQ as USER_SEQ4_0_0_,
auths0_.AUTH_SEQ as AUTH_SEQ1_0_0_,
auths0_.auth as auth2_0_0_,
auths0_.AUTH_SEQ as AUTH_SEQ1_0_1_,
auths0_.auth as auth2_0_1_,
auths0_.expiry as expiry3_0_1_,
auths0_.user_seq as user_seq4_0_1_,
user1_.seq as seq1_1_2_,
user1_.name as name2_1_2_,
user1_.password as password3_1_2_
from
Auth auths0_
left outer join
User user1_
on auths0_.user_seq=user1_.seq
where
auths0_.USER_SEQ=?
18:04:19 TRACE : binding parameter [1] as [INTEGER] - [100]
즉시로딩 FetchType.EAGER
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
Integer seq;
String name;
String password;
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JoinColumn(name = "USER_SEQ")
List<Auth> auths = null;
}
즉시 조인 쿼리가 날라간다.
select
user0_.seq as seq1_1_0_,
user0_.name as name2_1_0_,
user0_.password as password3_1_0_,
auths1_.USER_SEQ as USER_SEQ4_0_1_,
auths1_.AUTH_SEQ as AUTH_SEQ1_0_1_,
auths1_.auth as auth2_0_1_,
auths1_.AUTH_SEQ as AUTH_SEQ1_0_2_,
auths1_.auth as auth2_0_2_,
auths1_.expiry as expiry3_0_2_,
auths1_.user_seq as user_seq4_0_2_,
user2_.seq as seq1_1_3_,
user2_.name as name2_1_3_,
user2_.password as password3_1_3_
from
User user0_
left outer join
Auth auths1_
on user0_.seq=auths1_.USER_SEQ
left outer join
User user2_
on auths1_.user_seq=user2_.seq
where
user0_.seq=?
18:09:42 TRACE : binding parameter [1] as [INTEGER] - [100]
@ManyToMany
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME,
sequenceName = User.SEQUENCE_NAME, initialValue =
100)
public class User {
public static final String SEQUENCE_NAME =
"USER_SEQ";
@Id
@GeneratedValue(strategy =
GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "USER_SEQ",
referencedColumnName = "SHOP_SEQ", nullable = false)
List<CoffeShop> shops = null;
}
@Data
@Entity
//@IdClass(CoffeShop.class)
@SequenceGenerator(name = CoffeShop.SEQUENCE_NAME,
sequenceName = CoffeShop.SEQUENCE_NAME, initialValue = 100)
public class CoffeShop implements Serializable {
public static final String SEQUENCE_NAME = "SHOP_SEQ";
@Id
@Column(name = "SHOP_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = SEQUENCE_NAME)
Integer seq;
String number;
Date open;
Date close;
@ManyToMany(cascade = CascadeType.ALL,mappedBy = "shops")
List<User> users = null;
}
Session session =
getSessionFactory().getCurrentSession();
session.beginTransaction();
User user1 = new User();
user1.setName("name1");
User user2 = new User();
user2.setName("name2");
CoffeShop shop1 = new CoffeShop();
shop1.setName("shop1");
CoffeShop shop2 = new CoffeShop();
shop2.setName("shop2");
user1.setShops(Arrays.asList(shop1,shop2));
user2.setShops(Arrays.asList(shop1,shop2));
session.save(user1);
session.save(user2);
session.getTransaction().commit();
insert
into
User
(name, USER_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [VARCHAR] - [name1]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100]
insert
into
CoffeShop
(close, name, open, SHOP_SEQ)
values
(?, ?, ?, ?)
15:53:09 TRACE : binding parameter [1] as [TIMESTAMP] - [null]
15:53:09 TRACE : binding parameter [2] as [VARCHAR] - [shop1]
15:53:09 TRACE : binding parameter [3] as [TIMESTAMP] - [null]
15:53:09 TRACE : binding parameter [4] as [INTEGER] - [100]
insert
into
CoffeShop
(close, name, open, SHOP_SEQ)
values
(?, ?, ?, ?)
15:53:09 TRACE : binding parameter [1] as [TIMESTAMP] - [null]
15:53:09 TRACE : binding parameter [2] as [VARCHAR] - [shop2]
15:53:09 TRACE : binding parameter [3] as [TIMESTAMP] - [null]
15:53:09 TRACE : binding parameter [4] as [INTEGER] - [101]
insert
into
User
(name, USER_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [VARCHAR] - [name2]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101]
15:53:09 DEBUG : Executing batch size: 1
insert
into
User_CoffeShop
(users_USER_SEQ, shops_SHOP_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [INTEGER] - [100]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100]
insert
into
User_CoffeShop
(users_USER_SEQ, shops_SHOP_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [INTEGER] - [100]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101]
15:53:09 DEBUG : Done inserting collection: 2 rows inserted
15:53:09 DEBUG : Inserting collection: [com.khh.hibernate.c2.join.manytomany.entity.User.shops#101]
insert
into
User_CoffeShop
(users_USER_SEQ, shops_SHOP_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [INTEGER] - [101]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100]
insert
into
User_CoffeShop
(users_USER_SEQ, shops_SHOP_SEQ)
values
(?, ?)
15:53:09 TRACE : binding parameter [1] as [INTEGER] - [101]
15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101]
CoffeShop shopBydb = session.get(CoffeShop.class,100);
shopBydb.getUsers().forEach(it->log.debug("-->"+it.getName()));
session.getTransaction().commit();
select
users0_.shops_SHOP_SEQ as shops_SH2_2_0_,
users0_.users_USER_SEQ as users_US1_2_0_,
user1_.USER_SEQ as USER_SEQ1_1_1_,
user1_.name as name2_1_1_
from
User_CoffeShop users0_
inner join
User user1_
on users0_.users_USER_SEQ=user1_.USER_SEQ
where
users0_.shops_SHOP_SEQ=?
15:59:43 TRACE : binding parameter [1] as [INTEGER] - [100]
15:59:43 DEBUG : -->name1
15:59:43 DEBUG : -->name2
N:N 연결하기
@Data
@Entity
@SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100)
public class User {
public static final String SEQUENCE_NAME = "USER_SEQ";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
List<UserShop> shops = null;
}
@Entity
@SequenceGenerator(name = CoffeShop.SEQUENCE_NAME, sequenceName = CoffeShop.SEQUENCE_NAME, initialValue = 100)
public class CoffeShop implements Serializable {
public static final String SEQUENCE_NAME = "SHOP_SEQ";
@Id
@Column(name = "SHOP_SEQ")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
Integer seq;
String name;
Date open;
Date close;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "shop")
List<UserShop> shops = null;
}
@Data
public class UserShopPK implements Serializable {
@Column(name = "USER_SEQ")
Integer user_seq;
@Column(name = "SHOP_SEQ")
Integer shop_seq;
public UserShopPK() {}
public UserShopPK(Integer user_seq, Integer shop_seq){
this.user_seq = user_seq;
this.shop_seq = shop_seq;
}
}
@Data
@Entity
@IdClass(UserShopPK.class)
public class UserShop{
@Id
Integer user_seq;
@Id
Integer shop_seq;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="USER_SEQ", referencedColumnName = "USER_SEQ", insertable = false, updatable = false)
private User user;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="SHOP_SEQ",referencedColumnName = "SHOP_SEQ", insertable = false, updatable = false)
private CoffeShop shop;
@Temporal(TemporalType.TIMESTAMP)
// @ColumnDefault("now()")
@Version
Date reg;
}
create table CoffeShop (
SHOP_SEQ integer not null,
close timestamp,
name varchar(255),
open timestamp,
primary key (SHOP_SEQ)
)
create table User (
USER_SEQ integer not null,
name varchar(255),
primary key (USER_SEQ)
)
create table UserShop (
SHOP_SEQ integer not null,
USER_SEQ integer not null,
reg timestamp,
primary key (SHOP_SEQ, USER_SEQ)
)
alter table UserShop
add constraint FKxjfywgcu7nyi1lfdq0elefqx
foreign key (SHOP_SEQ)
references CoffeShop
alter table UserShop
add constraint FK7t3g73sa7nsvtdikuq8lon55
foreign key (USER_SEQ)
references User
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
User user1 = new User();
user1.setName("name1");
User user2 = new User();
user2.setName("name2");
CoffeShop shop1 = new CoffeShop();
shop1.setName("shop1");
CoffeShop shop2 = new CoffeShop();
shop2.setName("shop2");
session.save(user1);
session.save(user2);
session.save(shop1);
session.save(shop2);
UserShop userShop = new UserShop();
userShop.setUser_seq(user1.getSeq());
userShop.setShop_seq(shop1.getSeq());
session.save(userShop);
userShop = new UserShop();
userShop.setUser_seq(user1.getSeq());
userShop.setShop_seq(shop2.getSeq());
session.save(userShop);
session.flush();
session.clear();
UserShop byDB = session.get(UserShop.class,new UserShopPK(100,101));
log.debug(byDB.getUser().getName()+"<--->"+byDB.getShop().getName());
session.getTransaction().commit();
insert
into
User
(name, USER_SEQ)
values
(?, ?)
16:55:03 TRACE : binding parameter [1] as [VARCHAR] - [name1]
16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100]
16:55:03 DEBUG : Reusing batch statement
16:55:03 DEBUG :
insert
into
User
(name, USER_SEQ)
values
(?, ?)
16:55:03 TRACE : binding parameter [1] as [VARCHAR] - [name2]
16:55:03 TRACE : binding parameter [2] as [INTEGER] - [101]
16:55:03 DEBUG : Executing batch size: 2
16:55:03 DEBUG :
insert
into
CoffeShop
(close, name, open, SHOP_SEQ)
values
(?, ?, ?, ?)
16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [null]
16:55:03 TRACE : binding parameter [2] as [VARCHAR] - [shop1]
16:55:03 TRACE : binding parameter [3] as [TIMESTAMP] - [null]
16:55:03 TRACE : binding parameter [4] as [INTEGER] - [100]
16:55:03 DEBUG : Reusing batch statement
16:55:03 DEBUG :
insert
into
CoffeShop
(close, name, open, SHOP_SEQ)
values
(?, ?, ?, ?)
16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [null]
16:55:03 TRACE : binding parameter [2] as [VARCHAR] - [shop2]
16:55:03 TRACE : binding parameter [3] as [TIMESTAMP] - [null]
16:55:03 TRACE : binding parameter [4] as [INTEGER] - [101]
16:55:03 DEBUG : Executing batch size: 2
16:55:03 DEBUG :
insert
into
UserShop
(reg, SHOP_SEQ, USER_SEQ)
values
(?, ?, ?)
16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [2016-12-18 16:55:03.706]
16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100]
16:55:03 TRACE : binding parameter [3] as [INTEGER] - [100]
16:55:03 DEBUG : Reusing batch statement
16:55:03 DEBUG :
insert
into
UserShop
(reg, SHOP_SEQ, USER_SEQ)
values
(?, ?, ?)
16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [2016-12-18 16:55:03.73]
16:55:03 TRACE : binding parameter [2] as [INTEGER] - [101]
16:55:03 TRACE : binding parameter [3] as [INTEGER] - [100]
16:55:03 DEBUG : Executing batch size: 2
16:55:03 DEBUG :
select
usershop0_.SHOP_SEQ as SHOP_SEQ1_2_0_,
usershop0_.USER_SEQ as USER_SEQ2_2_0_,
usershop0_.reg as reg3_2_0_,
coffeshop1_.SHOP_SEQ as SHOP_SEQ1_0_1_,
coffeshop1_.close as close2_0_1_,
coffeshop1_.name as name3_0_1_,
coffeshop1_.open as open4_0_1_,
user2_.USER_SEQ as USER_SEQ1_1_2_,
user2_.name as name2_1_2_
from
UserShop usershop0_
left outer join
CoffeShop coffeshop1_
on usershop0_.SHOP_SEQ=coffeshop1_.SHOP_SEQ
left outer join
User user2_
on usershop0_.USER_SEQ=user2_.USER_SEQ
where
usershop0_.SHOP_SEQ=?
and usershop0_.USER_SEQ=?
16:55:03 TRACE : binding parameter [1] as [INTEGER] - [101]
16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100]
집고 넘어가기 : 여기서 알수 있듯. ManyToOne쪽에서
@JoinColumn(name="SHOP_SEQ",referencedColumnName = "SHOP_SEQ", insertable = false, updatable = false)
name은 자기자신 테이블의 컬럼명이다 (FK)
관계테이블은 inser와 update가 이루어지면 안되므로 false를 주었다
▼ 실행
User userBydb = session.get(User.class,100);
select
user0_.USER_SEQ as USER_SEQ1_1_0_,
user0_.name as name2_1_0_
from
User user0_
where
user0_.USER_SEQ=?
09:55:20 TRACE : binding parameter [1] as [INTEGER] - [100]
select
shops0_.USER_SEQ as USER_SEQ2_2_0_,
shops0_.SHOP_SEQ as SHOP_SEQ1_2_0_,
shops0_.SHOP_SEQ as SHOP_SEQ1_2_1_,
shops0_.USER_SEQ as USER_SEQ2_2_1_,
shops0_.reg as reg3_2_1_,
coffeshop1_.SHOP_SEQ as SHOP_SEQ1_0_2_,
coffeshop1_.close as close2_0_2_,
coffeshop1_.name as name3_0_2_,
coffeshop1_.open as open4_0_2_
from
UserShop shops0_
left outer join
COFFESHOP coffeshop1_
on shops0_.SHOP_SEQ=coffeshop1_.SHOP_SEQ
where
shops0_.USER_SEQ=?
09:55:20 TRACE : binding parameter [1] as [INTEGER] - [100]
캐싱
1 차 캐시
Session 객체와 관련하여 트랜잭션이 보장되는 캐시다. 이는 세션의 수명이 유지되는 동안이나
컨버세이션 conversation 내에서만 가능하다. 1 차 캐시는 하이버네이트 프레임워크에서 기본으로 제공한다.
//팩토리 가져오기
SessionFactory factory = d.getSessionFactory();
//세션가져오기
Session session = factory.getCurrentSession();
//트랜젝션 시작
session.beginTransaction();
User user = new User("admin","admin_pwd");
Set<Auth> auths = new HashSet<Auth>();
auths.add(new Auth("fName1","fCode","fVal"));
auths.add(new Auth("fName2","fCode","fVal"));
auths.add(new Auth("fName3","fCode","fVal"));
user.setAuths(auths);
session.save(user);
user = new User("user","user_pwd");
auths = new HashSet<Auth>();
auths.add(new Auth("fName1_1","fCode","fVal"));
auths.add(new Auth("fName1_2","fCode","fVal"));
auths.add(new Auth("fName1_3","fCode","fVal"));
user.setAuths(auths);
session.save(user);
//1차 캐쉬부분 읽어오기
User load_user = (User) session.load(User.class,new Integer(50));
System.out.println(load_user);
load_user.setId("admin_after");
session.save(user);
//트랜젝션 커밋
session.getTransaction().commit();
//트랜젝션이 닫혔기때문에 아래 부분은 오류남
User after_load_user = (User) session.load(User.class,new Integer(50));
System.out.println(after_load_user);
Hibernate: drop table T_AUTH if exists
Hibernate: drop table T_USER if exists
Hibernate: drop table T_USER_PRIVACY if exists
Hibernate: drop sequence SEQ_USER
Hibernate: create table T_AUTH (FNC_ID integer generated by default as identity (start with 1), fncCode
varchar(255), fncName varchar(255), fncValue varchar(255), user_SEQ integer, primary key (FNC_ID))
Hibernate: create table T_USER (SEQ integer not null, ID varchar(255), PWD varchar(255), primary key
(SEQ))
Hibernate: create table T_USER_PRIVACY (SEQ integer generated by default as identity (start with 1), AGE
integer, NAME varchar(255), primary key (SEQ))
Hibernate: alter table T_AUTH add constraint FK_5wrx276k6vlwxjehoqh6h1rwa foreign key (user_SEQ)
references T_USER
Hibernate: create sequence SEQ_USER start with 1
Hibernate: call next value for SEQ_USER
Hibernate: insert into T_USER (ID, PWD, SEQ) values (?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
Hibernate: insert into T_USER (ID, PWD, SEQ) values (?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values
(default, ?, ?, ?, ?)
User(seq=50, id=admin, password=admin_pwd, auths=[Auth(fncId=1, fncName=fName2, fncCode=fCode,
fncValue=fVal, user=null), Auth(fncId=2, fncName=fName3, fncCode=fCode, fncValue=fVal, user=null),
Auth(fncId=3, fncName=fName1, fncCode=fCode, fncValue=fVal, user=null)])
Hibernate: update T_USER set ID=?, PWD=? where SEQ=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=?
Exception in thread "main" org.hibernate.SessionException: Session is closed!
두 번째로 User 객체를 불렀을 때(load) 세션 자체가 가진 캐시에서 해당 객체를 조회한다. 이렇게
데이터베이스를 이용하는 네트워 크 라운드트립roundtrip을 피한다. 세션 캐시는 클래스 타입과 함께 명시되므로
이미 존재하는 인스턴스를 오버라이드할 때 좀 더 주의해야 한다.
2 차 캐시
SessionFactory 클래스를 이용하여 전역에서 사용할 수 있다.
그래서 2 차 캐시 안에 있는 어떤 데이터라도 애플리케이션 전체에서 사용이 가능하다.
하이버네이트는 EhCache 와 InfiniSpan 같은 오픈소스 캐시 라이브러리를 지원한다.
사용자 정의 캐시 라이브러리를 사용하려면 org.hibernate.cache.spi.CacheProvider 인터페이스
관련 라이브러리를 구현하면 된다. 하이버네이트는 기본 옵션으로 EhCache 를 2 차 캐시 공급자로 사용한다.
캐시 공급자를 연결하려면 hibernate.cache.provider 클래스 속성에 캐시 공급자를 명시한다.
다음은 JBoss 의 InifiniSpan 을 캐시 공급자로 연결하는 예다.
<hibernate-configuration>
<session-factory>
....
<!-- Infinispan 캐시 공급자 지정 -->
<property name="hibernate.cache.provider_class">
org.hibernate.cache.infinispan.InfinispanRegionFactory
</property>
</session-factory>
</hibernate-configuration>
다양한 캐시 속성을 이용하여 캐싱할 클래스별로 캐싱 정책을 지정할 수 있다. 매
핑 정의 파일의 class 태그 안에 cache 속성을 확인해 보자.
cache usage 속성
transactional 이 전략은 트랜잭션이 가능한 캐시를 지원하는 캐시 공급자를 위해 제공된다. 모
든 캐시 공급자가 트랜잭션이 가능한 캐싱 상품을 가지고 있지 않다는 점에 유의
하자.
read-only 갱신할 필요가 없는 영속화 객체에 자주 접근한다면 read-only 를 선택한다. 데
이터베이스를 거의 변경하지 않거나 전혀 변경하지 않는다면 이 옵션으로 성능
이 크게 향상할 것이다.
read-write 객체를 데이터베이스에서 읽거나 데이터베이스에 쓸 때 이 방법을 사용한다.
nonstrict-read-write 객체를 그다지 자주 갱신하지 않을 때 사용한다.
이 옵션을 전역에서 사용하려면 설정 파일에서 hibernate.cache.default_cache_concurrency_strategy
속성을 설정한다.
쿼리 캐시
객체 뿐만 아니라 쿼리도 캐싱할 수도 있다. 특정 쿼리를 빈번하게 사용한다면 캐싱하는 것을 추천한다.
이 기능을 사용하려면 hibernate.cache.use_query_cache 속성을 true 로 지정한다.
코드에 한 가지를 더 추가해야 하는데, Query.setCacheable() 메소드를 호출해서 Query 의
cacheable 속성을 true 로 지정해야한다.
상속 전략
Entity 상속으로 처리하는 방법이 3가지 있다.
1. Table-per-Class 전략
2. Table-per-Subclass 전략
3. Table-per-Concrete-Class 전략
@Table-per-Class
기본값 @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@Data
@Entity
//@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 50, allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME)
Integer seq;
String name;
}
@Data
@Entity
public class UserBankPrivacy extends User{
String password;
}
@Data
@Entity
public class UserBodyPrivacy
extends User{
Integer age;
double weight;
double height;
}
@Data
@Entity
public class UserOfficePrivacy
extends User{
String address;
}
create table USER (
seq integer not null,
name varchar(255),
age integer,
height double,
weight double,
password varchar(255),
address varchar(255),
primary key (seq)
)
▼실행
insert
into
User
(name, address, DTYPE, seq)
values
(?, ?, 'UserOfficePrivacy', ?)
16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name1]
16:31:06 TRACE : binding parameter [2] as [VARCHAR] - [addr1]
16:31:06 TRACE : binding parameter [3] as [INTEGER] - [50]
insert
into
User
(name, age, height, weight, DTYPE, seq)
values
(?, ?, ?, ?, 'UserBodyPrivacy', ?)
16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name2]
16:31:06 TRACE : binding parameter [2] as [INTEGER] - [2]
16:31:06 TRACE : binding parameter [3] as [DOUBLE] - [2.0]
16:31:06 TRACE : binding parameter [4] as [DOUBLE] - [2.0]
16:31:06 TRACE : binding parameter [5] as [INTEGER] - [51]
16:31:06 DEBUG :
insert
into
User
(name, password, DTYPE, seq)
values
(?, ?, 'UserBankPrivacy', ?)
16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name2]
16:31:06 TRACE : binding parameter [2] as [VARCHAR] - [pwd]
16:31:06 TRACE : binding parameter [3] as [INTEGER] - [52]
집고 넘어가기 : DTYPE 저것은 무엇일까 왜 들어가는걸까? 하이버네이트에서 자동으로 어떤 클래스에의해
INSERT가 되었는지 출처를 알수 있도록 DTYPE이라는 컬럼을 만들어 넣어버렸다. 문제를 해결해보자
▼DTYPE컬럼명을 변경해보자 @DiscriminatorColumn
@DiscriminatorColumn(name="CLASS_TYPE", discriminatorType=DiscriminatorType.STRING)
@SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue
= 50, allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME)
Integer seq;
String name;
}
▼DTYPE컬럼을 값을 각각 클래스 별로 다르게 지정해보자 @DiscriminatorValue
@Data
@Entity
@DiscriminatorValue(value="BANK")
public class UserBankPrivacy
extends User{
String password;
@Data
@Entity
@DiscriminatorValue(value="BODY")
public class UserBodyPrivacy extends
User{
Integer age;
@Data
@Entity
@DiscriminatorValue(value="OFFICE")
public class UserOfficePrivacy extends
User{
String address;
} double weight;
double height;
}
}
▼DTYPE컬럼을 없에보자 (사용하지 않기) @DiscriminatorFormula
@Data
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="CLASS_TYPE",
discriminatorType=DiscriminatorType.STRING)
@DiscriminatorFormula("...")
@DiscriminatorValue(value="USER")
@SequenceGenerator(name = User.SEQ_NAME, sequenceName =
User.SEQ_NAME, initialValue = 50, allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
SEQ_NAME)
Integer seq;
String name;
}
create table User (
seq integer not null,
name varchar(255),
age integer,
height double,
weight double,
password varchar(255),
address varchar(255),
primary key (seq)
)
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserOfficePrivacy userOffice = new UserOfficePrivacy();
userOffice.setName("name1");
userOffice.setAddress("addr1");
session.save(userOffice);
UserBodyPrivacy userBody = new UserBodyPrivacy();
userBody.setName("name2");
userBody.setAge(2);
userBody.setWeight(2.0);
userBody.setHeight(2.0);
session.save(userBody);
UserBankPrivacy userBank = new UserBankPrivacy();
userBank.setName("name2");
userBank.setPassword("pwd");
session.save(userBank);
session.getTransaction().commit();
insert
into
User
(name, address, seq)
values
(?, ?, ?)
16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name1]
16:40:56 TRACE : binding parameter [2] as [VARCHAR] - [addr1]
16:40:56 TRACE : binding parameter [3] as [INTEGER] - [50]
insert
into
User
(name, age, height, weight, seq)
values
(?, ?, ?, ?, ?)
16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name2]
16:40:56 TRACE : binding parameter [2] as [INTEGER] - [2]
16:40:56 TRACE : binding parameter [3] as [DOUBLE] - [2.0]
16:40:56 TRACE : binding parameter [4] as [DOUBLE] - [2.0]
16:40:56 TRACE : binding parameter [5] as [INTEGER] - [51]
insert
into
User
(name, password, seq)
values
(?, ?, ?)
16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name2]
16:40:56 TRACE : binding parameter [2] as [VARCHAR] - [pwd]
16:40:56 TRACE : binding parameter [3] as [INTEGER] - [52]
집고 넘어가기 : @Inheritance 어노테이션을 엔티티에 명시함으로써 Table-per-class 상속 전략을 정의한다. 이
어노테이션의 경우 strategy 변수를 통해 전략을 지정하는데, 여기서는 SINGLE_TABLE 전략을 사용한다.
그 외에도 InheritanceType 에는 TABLE_PER_CLASS 와 JOINED 전략이 있다. Table-per-class 전략을 이용할
때 InheritanceType.TABLE_PER_CLASS 값으로 잘못 지정할 수도 있다. 반드시 SINGLE_TABLE 만 사용하자.
Table-perclass
전략에서는 하위 클래스와 관련된 컬럼에는 NOT NULL 제약사항을 선언하지못한다는 점에 주의하자.
@Table-per-Subclass
Table-per-class 전략에서 구별자 컬럼을 이용하여 구분할 수있었지만
하나의 테이블을 이용하는 대신에 분리된 테이블을 사용하는 방법도 제공한다. @Table-per-subclass
@Inheritance(strategy=InheritanceType.JOINED)
@Data
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorValue(value="USER")
@SequenceGenerator(name = UserSubClass.SEQ_NAME, sequenceName = UserSubClass.SEQ_NAME, initialValue = 50,
allocationSize = 1)
public class UserSubClass {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME)
Integer seq;
String name;
}
Data
@Entity
public class UserBankPrivacy extends
UserSubClass{
String password;
}
@Data
@Entity
public class UserBodyPrivacy
extends UserSubClass{
Integer age;
double weight;
double height;
}
@Data
@Entity
public class UserOfficePrivacy extends
UserSubClass{
String address;
}
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
UserSubClass user = new UserSubClass();
user.setName("nameOffice");
session.save(user);
UserOfficePrivacy userOffice = new UserOfficePrivacy();
userOffice.setName("nameOffice");
userOffice.setAddress("addr");
session.save(userOffice);
UserBodyPrivacy userBody = new UserBodyPrivacy();
userBody.setName("nameBody");
userBody.setAge(2);
userBody.setWeight(2.0);
userBody.setHeight(2.0);
session.save(userBody);
UserBankPrivacy userBank = new UserBankPrivacy();
// userBank.setName("nameBank");
userBank.setPassword("pwd");
session.save(userBank);
session.getTransaction().commit();
집고 넘어가기 : 부모 Entity 의 TABLE 을 기준으로 자식 테이블 내용이 들어간다 userBank Entity 를 넣을때 보면
부모 Entity 의 name 값을 넣지 않아도 부모 시퀀스가 들어가고 난뒤 자식 Entity 의 테이블에 내용이 들어간다
부모 SEQ ID 컬럼과 자식들의 SEQ 컬럼은 자동 생성된다.(부모 ID Column 이름으로)
@Table-per-Concrete-Class
부모 클래스의 모든 속성이 자식 클래스와 관련된 테이블에 복사된다. (자주 쓰이진 않는다)
@Data
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@DiscriminatorValue(value="USER")
@SequenceGenerator(name = UserConcreteClass.SEQ_NAME, sequenceName = UserConcreteClass.SEQ_NAME, initialValue = 50,
allocationSize = 1)
public class UserConcreteClass {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME)
@Column(name = "USER_SEQ")
Integer seq;
String name;
}
@Data
@Entity
public class UserBankPrivacy
extends UserConcreteClass {
String password;
}
@Data
@Entity
public class UserBodyPrivacy extends
UserConcreteClass {
Integer age;
double weight;
double height;
}
@Data
@Entity
public class UserOfficePrivacy extends
UserConcreteClass {
String address;
}
하이버네이트 질의어
HQL(Hibernate Query Language)에서는 WHERE, ORDER BY, AVG, MAX 등을 SQL 처럼 사용할수 있습니다.
HQL 은 객체(Entity)를 사용합니다. 테이블을 나타내는 자리에 엔티티 객체 클래스명을 사용해야 합니다.
Query Class 사용하기
@Data
@Entity
@SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME,
allocationSize = 1)
public class User {
public static final String SEQ_NAME = "SEQ_USER";
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Column(name = "USER_SEQ")
Integer seq;
String name;
String password;
}
@Data
@Entity
public class Office {
@Id
@Column(name = "OFFICE_SEQ")
Integer seq;
String addr;
}
▼실행
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
User user = new User();
user.setName("nanme1");
user.setPassword("pwd1");
session.save(user);
Office office = new Office();
office.setSeq(user.getSeq());
office.setAddr("guro1");
session.save(office);
user = new User();
user.setName("nanme2");
user.setPassword("pwd2");
session.save(user);
office = new Office();
office.setSeq(user.getSeq());
office.setAddr("guro2");
session.save(office);
session.flush();
session.clear();
Query query = session.createQuery("from User");
List list = query.getResultList();
list.forEach(it->log.debug("-->"+it.toString()));
session.getTransaction().commit();
select
user0_.USER_SEQ as USER_SEQ1_1_,
user0_.name as name2_1_,
user0_.password as password3_1_
from
User user0_
11:05:18 DEBUG : -->User(seq=1, name=nanme1, password=pwd1)
11:05:18 DEBUG : -->User(seq=2, name=nanme2, password=pwd2)
▼실행 (where)
Query query = session.createQuery("from User as A where A.seq = 1");
select
user0_.USER_SEQ as USER_SEQ1_1_,
user0_.name as name2_1_,
user0_.password as password3_1_
from
User user0_
where
user0_.USER_SEQ=1
-->User(seq=1, name=nanme1, password=pwd1)
▼실행 (join) (모든객체 받아오기) Object[] 로받아온다.
Query query = session.createQuery("from User as A, Office as B where A.seq = 1 AND A.seq = B.seq");
List<Object[]> list = query.getResultList();
list.forEach(it->{
for (Object entity : it) {
log.debug("-->"+entity.toString());
}
});
select
user0_.USER_SEQ as USER_SEQ1_1_0_,
office1_.OFFICE_SEQ as OFFICE_S1_0_1_,
user0_.name as name2_1_0_,
user0_.password as password3_1_0_,
office1_.addr as addr2_0_1_
from
User user0_ cross
join
Office office1_
where
user0_.USER_SEQ=1
and user0_.USER_SEQ=office1_.OFFICE_SEQ
11:10:44 DEBUG : -->User(seq=1, name=nanme1, password=pwd1)
11:10:44 DEBUG : -->Office(seq=1, addr=guro1)
▼실행 (join) (특정객체 받아오기)
@Data
@Entity
public class Office {
@Id
@Column(name = "OFFICE_SEQ")
Integer seq;
String addr;
public Office() {
}
public Office(Integer seq, String addr){
this.seq = seq;
this.addr = addr;
}
}
필드를받을수 있는 생성자를 생성한다
Query query = session.createQuery("SELECT new Office (B.seq,B.addr)
from User as A, Office as B where A.seq = 1 AND A.seq = B.seq");
List<Office> list = query.getResultList();
list.forEach(it->log.debug("-->"+it.toString()));
select
office1_.OFFICE_SEQ as col_0_0_,
office1_.addr as col_1_0_
from
User user0_ cross
join
Office office1_
where
user0_.USER_SEQ=1
and user0_.USER_SEQ=office1_.OFFICE_SEQ
11:15:27 DEBUG : -->Office(seq=1, addr=guro1)
▼실행 (그룹함수)
Query query = session.createQuery("SELECT MAX(A.seq) from User as A");
Object result = query.getSingleResult();
log.debug("-->"+result);
select
max(user0_.USER_SEQ) as col_0_0_
from
User user0_
-->2
▼실행 (그룹함수 복합)
Query query = session.createQuery("SELECT MAX(A.seq), MIN(A.seq), AVG(A.seq), COUNT(*) from User as A");
Object[] results = (Object[]) query.getSingleResult();
for (Object entity : results) {
log.debug("-->"+entity.toString());
}
select
max(user0_.USER_SEQ) as col_0_0_,
min(user0_.USER_SEQ) as col_1_0_,
avg(cast(user0_.USER_SEQ as double)) as col_2_0_,
count(*) as col_3_0_
from
User user0_
11:27:35 DEBUG : -->2
11:27:35 DEBUG : -->1
11:27:35 DEBUG : -->1.5
11:27:35 DEBUG : -->2
▼HSQL에 변수 바인딩하기 ? 처리
Query query = session.createQuery("from User as A where A.seq = ?");
query.setParameter(0, new Integer(1));
▼HSQL에 변수 바인딩하기 치환이름 처리
Query query = session.createQuery("from User as A where A.seq = :seq");
query.setParameter("seq", new Integer(1));
▼HSQL에 변수 바인딩하기 in조건 list 처리
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)
Hibernate start (하이버네이트 시작하기)

More Related Content

What's hot

Spring Data JPA from 0-100 in 60 minutes
Spring Data JPA from 0-100 in 60 minutesSpring Data JPA from 0-100 in 60 minutes
Spring Data JPA from 0-100 in 60 minutesVMware Tanzu
 
Oracle Cloud Infrastructure:2021年5月度サービス・アップデート
Oracle Cloud Infrastructure:2021年5月度サービス・アップデートOracle Cloud Infrastructure:2021年5月度サービス・アップデート
Oracle Cloud Infrastructure:2021年5月度サービス・アップデートオラクルエンジニア通信
 
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...Insight Technology, Inc.
 
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!Akihiro Kitada
 
MongoDBのアレをアレする
MongoDBのアレをアレするMongoDBのアレをアレする
MongoDBのアレをアレするAkihiro Kuwano
 
LibreOffice API について
LibreOffice API についてLibreOffice API について
LibreOffice API について健一 辰濱
 
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)NTT DATA Technology & Innovation
 
使ってみよう!JDK Flight Recorder
使ってみよう!JDK Flight Recorder使ってみよう!JDK Flight Recorder
使ってみよう!JDK Flight RecorderYoshiro Tokumasu
 
よろしい、ならばMicro-ORMだ
よろしい、ならばMicro-ORMだよろしい、ならばMicro-ORMだ
よろしい、ならばMicro-ORMだNarami Kiyokura
 
Mongodb 특징 분석
Mongodb 특징 분석Mongodb 특징 분석
Mongodb 특징 분석Daeyong Shin
 
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...Insight Technology, Inc.
 
細かすぎて伝わらないD3 ver.4の話
細かすぎて伝わらないD3 ver.4の話細かすぎて伝わらないD3 ver.4の話
細かすぎて伝わらないD3 ver.4の話清水 正行
 
Workshop Spring - Session 1 - L'offre Spring et les bases
Workshop Spring  - Session 1 - L'offre Spring et les basesWorkshop Spring  - Session 1 - L'offre Spring et les bases
Workshop Spring - Session 1 - L'offre Spring et les basesAntoine Rey
 
そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?takezoe
 
Building ZingMe News Feed System
Building ZingMe News Feed SystemBuilding ZingMe News Feed System
Building ZingMe News Feed SystemChau Thanh
 
JSON:APIについてざっくり入門
JSON:APIについてざっくり入門JSON:APIについてざっくり入門
JSON:APIについてざっくり入門iPride Co., Ltd.
 
Mongo DB 성능최적화 전략
Mongo DB 성능최적화 전략Mongo DB 성능최적화 전략
Mongo DB 성능최적화 전략Jin wook
 

What's hot (20)

Spring Data JPA from 0-100 in 60 minutes
Spring Data JPA from 0-100 in 60 minutesSpring Data JPA from 0-100 in 60 minutes
Spring Data JPA from 0-100 in 60 minutes
 
Oracle Cloud Infrastructure:2021年5月度サービス・アップデート
Oracle Cloud Infrastructure:2021年5月度サービス・アップデートOracle Cloud Infrastructure:2021年5月度サービス・アップデート
Oracle Cloud Infrastructure:2021年5月度サービス・アップデート
 
Spring framework core
Spring framework coreSpring framework core
Spring framework core
 
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...
B23,B31 sap sybase iq ~全部話します。IQのカラムストア方式、ビットワイズインデックス、DQP、カラム圧縮、等々 by Toshih...
 
WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介WebSocket / WebRTCの技術紹介
WebSocket / WebRTCの技術紹介
 
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!
〜僕の初めてのリアクティブプログラミング Reactor を使ってリアクティブに昇龍拳を繰り出してみた!
 
MongoDBのアレをアレする
MongoDBのアレをアレするMongoDBのアレをアレする
MongoDBのアレをアレする
 
LibreOffice API について
LibreOffice API についてLibreOffice API について
LibreOffice API について
 
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
より速く より運用しやすく 進化し続けるJVM(Java Developers Summit Online 2023 発表資料)
 
使ってみよう!JDK Flight Recorder
使ってみよう!JDK Flight Recorder使ってみよう!JDK Flight Recorder
使ってみよう!JDK Flight Recorder
 
よろしい、ならばMicro-ORMだ
よろしい、ならばMicro-ORMだよろしい、ならばMicro-ORMだ
よろしい、ならばMicro-ORMだ
 
Mongodb 특징 분석
Mongodb 특징 분석Mongodb 특징 분석
Mongodb 특징 분석
 
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...
[db tech showcase Tokyo 2014] D17:こだわろう、一貫性! はじめよう、分散KVS!! ~分散KVSの弱点と、それを克服する...
 
細かすぎて伝わらないD3 ver.4の話
細かすぎて伝わらないD3 ver.4の話細かすぎて伝わらないD3 ver.4の話
細かすぎて伝わらないD3 ver.4の話
 
Workshop Spring - Session 1 - L'offre Spring et les bases
Workshop Spring  - Session 1 - L'offre Spring et les basesWorkshop Spring  - Session 1 - L'offre Spring et les bases
Workshop Spring - Session 1 - L'offre Spring et les bases
 
そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?そんなトランザクションマネージャで大丈夫か?
そんなトランザクションマネージャで大丈夫か?
 
JPA and Hibernate
JPA and HibernateJPA and Hibernate
JPA and Hibernate
 
Building ZingMe News Feed System
Building ZingMe News Feed SystemBuilding ZingMe News Feed System
Building ZingMe News Feed System
 
JSON:APIについてざっくり入門
JSON:APIについてざっくり入門JSON:APIについてざっくり入門
JSON:APIについてざっくり入門
 
Mongo DB 성능최적화 전략
Mongo DB 성능최적화 전략Mongo DB 성능최적화 전략
Mongo DB 성능최적화 전략
 

Similar to Hibernate start (하이버네이트 시작하기)

[Uws] enterprise application architecture, msa, java9, spring 소개
[Uws] enterprise application architecture, msa, java9, spring 소개[Uws] enterprise application architecture, msa, java9, spring 소개
[Uws] enterprise application architecture, msa, java9, spring 소개HYUN-JOO LEE
 
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)DK Lee
 
No sql survey report
No sql survey reportNo sql survey report
No sql survey reportGichan Lee
 
Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Hyosang Hong
 
Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Hyosang Hong
 
초고속 웹사이트 개발을 위한 Codeigniter PHP Framework
초고속 웹사이트 개발을 위한 Codeigniter PHP Framework초고속 웹사이트 개발을 위한 Codeigniter PHP Framework
초고속 웹사이트 개발을 위한 Codeigniter PHP FrameworkInseok Lee
 
보다 나은 웹 어플리케이션 설계
보다 나은 웹 어플리케이션 설계보다 나은 웹 어플리케이션 설계
보다 나은 웹 어플리케이션 설계Eb Styles
 
Sql 중심 코드 탈피 발표자료
Sql 중심 코드 탈피 발표자료Sql 중심 코드 탈피 발표자료
Sql 중심 코드 탈피 발표자료ssuser776e2d
 
Data oriented design
Data oriented designData oriented design
Data oriented designSangwook Kwon
 
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)Devgear
 
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료beom kyun choi
 
Sql 중심 코드 탈피
Sql 중심 코드 탈피Sql 중심 코드 탈피
Sql 중심 코드 탈피ssuser776e2d
 
Spring3 발표자료 - 김연수
Spring3 발표자료 - 김연수Spring3 발표자료 - 김연수
Spring3 발표자료 - 김연수Yeon Soo Kim
 
[아꿈사/110903] 도메인주도설계 4장
[아꿈사/110903] 도메인주도설계 4장[아꿈사/110903] 도메인주도설계 4장
[아꿈사/110903] 도메인주도설계 4장sung ki choi
 
스프링 스터디 1장
스프링 스터디 1장스프링 스터디 1장
스프링 스터디 1장Seongchan Kang
 
실무에서 라라벨 테스트를 작성하다 알게 된 것
실무에서 라라벨 테스트를 작성하다 알게 된 것실무에서 라라벨 테스트를 작성하다 알게 된 것
실무에서 라라벨 테스트를 작성하다 알게 된 것Hyun-Seok Lee
 
Java 그쪽 동네는
Java 그쪽 동네는Java 그쪽 동네는
Java 그쪽 동네는도형 임
 
[스프링 스터디 1일차] 오브젝트와 의존관계
[스프링 스터디 1일차] 오브젝트와 의존관계[스프링 스터디 1일차] 오브젝트와 의존관계
[스프링 스터디 1일차] 오브젝트와 의존관계AnselmKim
 
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리경식 최
 

Similar to Hibernate start (하이버네이트 시작하기) (20)

[Uws] enterprise application architecture, msa, java9, spring 소개
[Uws] enterprise application architecture, msa, java9, spring 소개[Uws] enterprise application architecture, msa, java9, spring 소개
[Uws] enterprise application architecture, msa, java9, spring 소개
 
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)
자바 웹 개발 시작하기 (5주차 : 스프링 프래임워크)
 
No sql survey report
No sql survey reportNo sql survey report
No sql survey report
 
Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료
 
Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료Jpa 쿼리 포함 자료
Jpa 쿼리 포함 자료
 
초고속 웹사이트 개발을 위한 Codeigniter PHP Framework
초고속 웹사이트 개발을 위한 Codeigniter PHP Framework초고속 웹사이트 개발을 위한 Codeigniter PHP Framework
초고속 웹사이트 개발을 위한 Codeigniter PHP Framework
 
보다 나은 웹 어플리케이션 설계
보다 나은 웹 어플리케이션 설계보다 나은 웹 어플리케이션 설계
보다 나은 웹 어플리케이션 설계
 
Sql 중심 코드 탈피 발표자료
Sql 중심 코드 탈피 발표자료Sql 중심 코드 탈피 발표자료
Sql 중심 코드 탈피 발표자료
 
Data oriented design
Data oriented designData oriented design
Data oriented design
 
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)
ER/Studio 데이터 모델링 솔루션으로 마이그레이션(from ERwin)
 
Java JPA
Java JPAJava JPA
Java JPA
 
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료
2007년 제8회 JCO 컨퍼런스 POJO 프로그래밍 발표 자료
 
Sql 중심 코드 탈피
Sql 중심 코드 탈피Sql 중심 코드 탈피
Sql 중심 코드 탈피
 
Spring3 발표자료 - 김연수
Spring3 발표자료 - 김연수Spring3 발표자료 - 김연수
Spring3 발표자료 - 김연수
 
[아꿈사/110903] 도메인주도설계 4장
[아꿈사/110903] 도메인주도설계 4장[아꿈사/110903] 도메인주도설계 4장
[아꿈사/110903] 도메인주도설계 4장
 
스프링 스터디 1장
스프링 스터디 1장스프링 스터디 1장
스프링 스터디 1장
 
실무에서 라라벨 테스트를 작성하다 알게 된 것
실무에서 라라벨 테스트를 작성하다 알게 된 것실무에서 라라벨 테스트를 작성하다 알게 된 것
실무에서 라라벨 테스트를 작성하다 알게 된 것
 
Java 그쪽 동네는
Java 그쪽 동네는Java 그쪽 동네는
Java 그쪽 동네는
 
[스프링 스터디 1일차] 오브젝트와 의존관계
[스프링 스터디 1일차] 오브젝트와 의존관계[스프링 스터디 1일차] 오브젝트와 의존관계
[스프링 스터디 1일차] 오브젝트와 의존관계
 
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리
마이크로 프론트엔드 아키텍쳐를 위한 모노레포 관리
 

Hibernate start (하이버네이트 시작하기)

  • 1. HIBER NATE 5.x 시작하기 수익성의 성격을 띄지 않는다면 재배포, 수정 가능합니다. visualkhh@gmail.com https://github.com/visualkhh/book-hibernate (2016. 12. 21)
  • 2. 목 차 ORM (Object Relational Mapping) 무엇인가?______________________________________________________________5 JPA (Java Persistence API) 무엇인가?_______________________________________________________________________5 HIBERNATE 무엇인가? ______________________________________________________________________________________5 왜 JPA 를 쓰는가? __________________________________________________________________________________________5 1. 기존 SQL 중심적인 개발시 불편하다 _________________________________________________________________5 2. 객체-관계 간 모델 불일치 ____________________________________________________________________________5 3. 상속 불일치____________________________________________________________________________________________6 4. 관계와 연관 관계의 불일치 ___________________________________________________________________________6 장단점_______________________________________________________________________________________________________6 장점_______________________________________________________________________________________________________6 단점_______________________________________________________________________________________________________6 JPA, HIBERNATE Architecture _____________________________________________________________________________7 엔티티 상태 및 생명주기___________________________________________________________________________________9 비영속 상태_______________________________________________________________________________________________9 영속 상태_________________________________________________________________________________________________9 준영속 상태_______________________________________________________________________________________________9 Hibernate 셋팅____________________________________________________________________________________________ 11 gradle___________________________________________________________________________________________________ 11 hibernate.cfg.xml _______________________________________________________________________________________ 13 Session ____________________________________________________________________________________________________ 14 Session API _____________________________________________________________________________________________ 14 SessionFactory__________________________________________________________________________________________ 14 Hibernate-provided BasicTypes___________________________________________________________________________ 15 Entity ______________________________________________________________________________________________________ 18 @Entity _________________________________________________________________________________________________ 18 식별자_____________________________________________________________________________________________________ 19 @GeneratedValue ______________________________________________________________________________________ 19 복합 식별자 ID (KEY) _____________________________________________________________________________________ 22
  • 3. 키선언 첫번째 방법 (@Embeddable) __________________________________________________________________ 23 키선언 두번째 방법 (@EmbeddedId) __________________________________________________________________ 24 키선언 세번째 방법 (@IdClass) ________________________________________________________________________ 25 Join 조인__________________________________________________________________________________________________ 26 연관 관계_______________________________________________________________________________________________ 26 다중성 ________________________________________________________________________________________________ 26 방향성 ________________________________________________________________________________________________ 26 조인전에 먼저 알아야될 Cascade______________________________________________________________________ 26 1:1 @OneToOne________________________________________________________________________________________ 27 방향성을 갖자________________________________________________________________________________________ 31 @OneToMany, @ManyToOne__________________________________________________________________________ 32 1:N, N:1, N:N 컬렉션 영속화 (List, Set, Map, Array[]) _______________________________________________ 32 방향성 갖자 __________________________________________________________________________________________ 43 조회하기______________________________________________________________________________________________ 43 @ManyToMany_________________________________________________________________________________________ 46 N:N 연결하기 _________________________________________________________________________________________ 49 캐싱 _______________________________________________________________________________________________________ 54 1 차 캐시 _______________________________________________________________________________________________ 54 2 차 캐시 _______________________________________________________________________________________________ 55 cache usage 속성 ______________________________________________________________________________________ 55 쿼리 캐시_______________________________________________________________________________________________ 56 상속 전략 _________________________________________________________________________________________________ 57 @Table-per-Class_______________________________________________________________________________________ 57 @Table-per-Subclass ___________________________________________________________________________________ 61 @Table-per-Concrete-Class ____________________________________________________________________________ 62 하이버네이트 질의어 _____________________________________________________________________________________ 63 Query Class 사용하기 __________________________________________________________________________________ 63 @Embedded Objects _____________________________________________________________________________________ 67 @ElementCollection ______________________________________________________________________________________ 68 Pagination 페이지네이션 _________________________________________________________________________________ 70
  • 4. Criteria ____________________________________________________________________________________________________ 71 네임드 쿼리 _______________________________________________________________________________________________ 74 네이티브 쿼리_____________________________________________________________________________________________ 76 Groovy Template 이용하여 Dynamic Query 사용하기___________________________________________________ 77
  • 5. ORM (Object Relational Mapping) 무엇인가? RDB 테이블을 객체지향적으로 사용하기 위한 기술입니다. RDB 은 객체지향적 (상속, 다형성, 레퍼런스, 오브젝트 등)으로 접근하기 쉽지 않습니다. 때문에 ORM 을 사용해 오브젝트와 RDB 사이에 객체지향적으로 다루기 위한 기술입니다. JPA (Java Persistence API) 무엇인가? ORM 전문가가 참여한 EJB 3.0 스펙 작업에서 기존 EJB ORM 이던 Entity Bean 을 JPA 라고 바꾸고 JavaSE, JavaEE 를 위한 영속성(persistence) 관리와 ORM 을 위한 표준 기술입니다. JPA 는 ORM 표준 기술로 Hibernate, OpenJPA, EclipseLink, TopLink Essentials 과 같은 구현체가 있고 이에 표준 인터페이스가 바로 JPA 입니다. HIBERNATE 무엇인가? Boss 에서 개발한 ORM(Object Relational Mapping) 프레임워크 입니다. 장점 Hibernate 는 특정 클래스에 매핑되어야 하는 데이터베이스의 테이블에 대한 관계 정의가 되어 있는 XML 파일의 메타데이터로 객체관계 매핑을 간단하게 수행시킵니다. Hibernate 를 사용하면 데이터베이스가 변경되더라도 SQL 스크립트를 수정하는등의 작업을 할 필요가 없습니다. 애플리케이션에서 사용되는 데이터베이스를 변경시키고자 한다면 설정파일의 dialect 프로퍼티를 수정함으로서 쉽게 처리할 수 있습니다. Hibernate 는 MySQL, Oracle, Sybase, Derby, PostgreSQL 를 포함한 많은 데이터베이스를 지원하며 POJO 기반의 모델과도 원활하게 동작합니다. 왜 JPA 를 쓰는가? 1. 기존 SQL 중심적인 개발시 불편하다 - 쿼리가 변경되면 이에따른 프로그램 소스 DTO 객체의 변경도 불가피하게 일어난다 - 데이터를 가져와 객체지향적으로 관계를 Mapping 하는 일이 매번 일어난다. !!SQL 의존적인 개발이 이루어진다. 2. 객체-관계 간 모델 불일치 관계형 데이터베이스에는 로우와 컬럼의 2 차원 형태로 데이터가 저장된다. 데이터 관계는 외래키 foreign key 형태로 표현된다. 문제는 도메인 객체를 관계형 데이터 베이스로 저장할 때 발생한다. 애플리케이션의 객체는 로우와 컬럼 형태가 아니다. 도메인 객체는 객체의 상태를 속성(변수)으로 가지고 있다. 그래서 도메인 객체 그대로 관계형 데이터베이스에 저장할 수가 없다. 이러한 불일치를 객체-관계 간 임피던스 불일치 object- relational impedance mismatch 라고 합니다.
  • 6. 3. 상속 불일치 상속은 객체 세계에서는 지원하지만, 관계형 스키마에서는 지원하지 않는다. 상속은 모든 객체지향 언어, 특히 자바에서 바늘과 실처럼 뗄 수 없는 특징입니다. 안타깝게도 관계형 스키마에는 상속 개념이 없습니다. 회사에서 임원과 직원의 예를 들어보면. 임원 개인도 회사의 직원이죠. 이 관계를 데이터베이스에서 표현하는 것은 테이블 간 관계 수정이 필요해서 쉽지 않습니다. 상속 없이 현실 세계의 문제 상황을 표현하는 것은 매우 복잡한 일입니다. 그런데 데이터베이스는 상속 관계와 같은 형태를 알지 못하지요. 이것을 해결할 간단한 방법은없지만, 문제를 풀 수 있는 몇 가지 접근법이 있습니다. 이 접근법은 다양한 클래스-테이블 class-to-table 전략을 사용합니다. 4. 관계와 연관 관계의 불일치 1. SQL 중심적인 개발의 문제점 - field 하나추가시 쿼리도 바꿔야하고 VO도 바꿔야되고 ... - SQL에 의존적인 개발을 피하기 어렵다. - 객체답게 모델링 할수록 매핑 작업만 늘어난다 장단점 장점 - 객체지향적으로 데이터를 관리할 수 있기 때문에 비즈니스 로직에 집중 할 수 있으며, 객체지향 개발이 가능합니다. - 테이블 생성, 변경, 관리가 쉽다. (JPA 를 잘 이해하고 있는 경우) 로직을 쿼리에 집중하기 보다는 객체자체에 집중 할 수 있습니다. - 빠른 개발이 가능합니다. 단점 - 어렵다. 장점을 더 극대화 하기 위해서 알아야 할게 많습니다. - 잘 이해하고 사용하지 않으면 독이될수도 있습니다. - 성능상 문제가 있을 수 있다.(이 문제 또한 잘 이해해야 해결이 가능합니다.
  • 7. JPA, HIBERNATE Architecture Application Hibernate JPA JDBC API DB Application JPA 인터페이스 Hibernate, EclipseLink, DataNucleus..등 ...
  • 8.
  • 9. 엔티티 상태 및 생명주기 비영속 상태 퍼시스턴트 객체를 처음 만들었을 때의 상태. 데이터베이스 테이블에 관련 데이터가 없으며, 연관된 Session 이 없다. 영속 상태 현재 활성화된 Session 과 연결된 퍼시스턴트 객체. 이 상태의 퍼시스턴트 객체는 고유성을 가지며, 프로퍼티 값의 변경이 Session 을 통해 자동으로 데이터베이스에 반영된다. 준영속 상태 영속 상태의 퍼시스턴트 객체가 Session 과 연결이 끊기면 준영속 상태가 된다. Hibernate 의 관리를 받지는 않지만, 영속 데이터를 갖고 있다.
  • 11. Hibernate 셋팅 gradle group 'com.khh' version '1.0-SNAPSHOT' apply plugin: 'java' sourceCompatibility = 1.5 repositories { mavenCentral() } dependencies { /* slf4j */ compile(group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21') /* hibernate */ compile(group: 'org.hibernate', name: 'hibernate-core', version: '5.2.5.Final') /* lombok */ compile(group: 'org.projectlombok', name: 'lombok', version: '1.16.8') /*groovy*/ compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.6' /* class scaner */ compile (group: 'net.sf.corn', name: 'corn-cps', version: '1.1.7') /* h2 database */ compile(group: 'com.h2database', name: 'h2', version: '1.4.193') /* logback-classic */ compile(group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.8') testCompile(group: 'junit', name: 'junit', version: '4.11') } build.gradle
  • 12. Hibernate Setting (Properties Set) package com.khh.hibernate.c0; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.h2.tools.Server; import org.hibernate.SessionFactory; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import java.sql.SQLException; @Data @Slf4j public class HibernateSetting implements Runnable{ Server server = null; SessionFactory sessionFactory = null; public HibernateSetting() throws SQLException { //H2 Database Start server = Server.createWebServer("-web", "-webAllowOthers", "-webPort", "8088").start(); //외부 XML 으로 설정 Configuration configuration = getHibernateConfigByCode(); //CODE BASE 로 설정 //Configuration configuration = getHibernateConfigByCode(); //Build StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()); sessionFactory = configuration.buildSessionFactory(serviceRegistryBuilder.build()); configuration.buildSessionFactory(serviceRegistryBuilder.build()); } public Configuration getHibernateConfigByXML() { Configuration configuration = new Configuration(); configuration.configure("hibernate.cfg.xml"); return configuration; } public Configuration getHibernateConfigByCode() { Configuration configuration = new Configuration(); configuration.setProperty(Environment.DIALECT, "org.hibernate.dialect.H2Dialect"); configuration.setProperty(Environment.DRIVER, "org.h2.Driver"); configuration.setProperty(Environment.URL, "jdbc:h2:mem:test"); configuration.setProperty(Environment.USER, "sa"); configuration.setProperty(Environment.POOL_SIZE, "55"); configuration.setProperty(Environment.STATEMENT_BATCH_SIZE, "30"); configuration.setProperty(Environment.AUTOCOMMIT, "true"); configuration.setProperty(Environment.SHOW_SQL, "true"); configuration.setProperty(Environment.FORMAT_SQL, "true"); configuration.setProperty(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread"); configuration.setProperty(Environment.HBM2DDL_AUTO, "create-drop"); return configuration; } public void run() { //log.debug("start"); } public static void main(String[] arg) throws SQLException { new HibernateSetting().run(); } } 매개변수로 표준 가상머신 Standard VM 인수 형식을 사용할수 있다 -Dhibernate.connection.url=jdbc:derby:memory:JH;create=true -Dhibernate.username=mk
  • 13. hibernate.cfg.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- http://www.tutorialspoint.com/hibernate/hibernate_configuration.htm https://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/session-configuration.html --> <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> <property name="hibernate.connection.driver_class">org.h2.Driver</property> <!-- <property name="hibernate.default_schema">user</property> --> <!-- Assume test is the database name --> <property name="hibernate.connection.url">jdbc:h2:mem:test</property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.connection.password"></property> <!-- JDBC connection pool --> <property name="hibernate.connection.pool_size">55</property> <property name="hibernate.jdbc.batch_size">30</property> <property name="hibernate.connection.autocommit">true</property> <!-- <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> --> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <!-- <property name="hibernate.use_sql_comments">true</property> --> <!-- current_session ( e.g. jta | thread | managed | custom.Class )--> <property name="hibernate.current_session_context_class">thread</property> <!-- hbm2ddl.auto ( e.g. validate | update | create | create-drop ) --> <property name="hbm2ddl.auto">create-drop</property> <!-- List of XML mapping files --> <!-- <mapping resource="Employee.hbm.xml" /> --> <!-- Infinispan 캐시 공급자 지정 --> <property name="hibernate.cache.provider_class"> org.hibernate.cache.infinispan.InfinispanRegionFactory </property> </session-factory> </hibernate-configuration>
  • 14. Session Session API org.hibernate.SessionFactory 클래스에서 제공하는 SessionFactory는 Session 인스턴스를 생성하는 팩토리 클래스 factory class다. 이는 Thread-safe한 객체이므로 데이터가 의도하지 않게 바뀌는 것을 염려하지 않고, 여러 클래 스에서사용해도 된다. Session 인스턴스가 생성될 때 매핑 정보도 함께 전달하므로 컴파 일된 형태로 모든 매핑 데이터를 가진다 SessionFactory 캐시의 두 번째 수준을 관리한다. 이는 애플리케이션을 구성하는 모든 컴포넌트에도 해당된다. 전역global 캐시는 데이터베이스에서 이미 가져온동일한 결과를 여러 애플리케이션에서 요청하는 경우 사용된다. SessionFactory가 데이터베이스 연결을 위한 열쇠 꾸러미를 가지고 있다면, Session은 데이터베이스 접속과 데이터 이동이 이루어지는 열쇠 자체다. Session은 싱글 스레드single-threaded 객체이므로 여러 컴퍼넌트에 같이 선언되어 사용해서는 안 된다. 하나의 작업 단위를 뜻한다. 팩토리에서 세션을 가져오려면 factory.getCurrentSession() 메소드를 사용한다. 세션 객체를 얻으면 한 개의 트랜잭션 안에서 데이터베이스 작업을 수행한다. 세션과 트랜잭션은 밀접한 관련이 있다. 다음은 세션과 트랜잭션의 생애 주기를 보여주고 있다.
  • 15. Hibernate-provided BasicTypes Table 1. Standard BasicTypes Hibernate type (org.hibernate.type package) JDBC type Java type BasicTypeRegistry key(s) StringType VARCHAR java.lang.String string, java.lang.String MaterializedClob CLOB java.lang.String materialized_clob TextType LONGVARCHAR java.lang.String text CharacterType CHAR char, java.lang.Character char, java.lang.Character BooleanType BIT boolean, java.lang.Boolean boolean, java.lang.Boolean NumericBooleanType INTEGER, 0 is false, 1 is true boolean, java.lang.Boolean numeric_boolean YesNoType CHAR, 'N'/'n' is false, 'Y'/'y' is true. The uppercase value is written to the database. boolean, java.lang.Boolean yes_no TrueFalseType CHAR, 'F'/'f' is false, 'T'/'t' is true. The uppercase value is written to the database. boolean, java.lang.Boolean true_false ByteType TINYINT byte, java.lang.Byte byte, java.lang.Byte ShortType SMALLINT short, java.lang.Short short, java.lang.Short IntegerTypes INTEGER int, java.lang.Integer int, java.lang.Integer LongType BIGINT long, java.lang.Long long, java.lang.Long FloatType FLOAT float, java.lang.Float float, java.lang.Float DoubleType DOUBLE double, java.lang.Double double, java.lang.Double BigIntegerType NUMERIC java.math.BigInteger big_integer, java.math.BigInteger BigDecimalType NUMERIC java.math.BigDecimal big_decimal, java.math.bigDecimal TimestampType TIMESTAMP java.sql.Timestamp timestamp, java.sql.Timestamp TimeType TIME java.sql.Time time, java.sql.Time DateType DATE java.sql.Date date, java.sql.Date CalendarType TIMESTAMP java.util.Calendar calendar, java.util.Calendar CalendarDateType DATE java.util.Calendar calendar_date CalendarTimeType TIME java.util.Calendar calendar_time CurrencyType java.util.Currency VARCHAR currency, java.util.Currency LocaleType VARCHAR java.util.Locale locale, java.utility.locale TimeZoneType VARCHAR, using the TimeZone ID java.util.TimeZone timezone, java.util.TimeZone UrlType VARCHAR java.net.URL url, java.net.URL ClassType VARCHAR (class FQN) java.lang.Class class, java.lang.Class BlobType BLOB java.sql.Blob blog, java.sql.Blob ClobType CLOB java.sql.Clob clob, java.sql.Clob BinaryType VARBINARY byte[] binary, byte[]
  • 16. Table 1. Standard BasicTypes Hibernate type (org.hibernate.type package) JDBC type Java type BasicTypeRegistry key(s) MaterializedBlobType BLOB byte[] materized_blob ImageType LONGVARBINARY byte[] image WrapperBinaryType VARBINARY java.lang.Byte[] wrapper-binary, Byte[], java.lang.Byte[] CharArrayType VARCHAR char[] characters, char[] CharacterArrayType VARCHAR java.lang.Character[] wrapper-characters, Character[], java.lang.Character[] UUIDBinaryType BINARY java.util.UUID uuid-binary, java.util.UUID UUIDCharType CHAR, can also read VARCHAR java.util.UUID uuid-char PostgresUUIDType PostgreSQL UUID, through Types#OTHER, which complies to the PostgreSQL JDBC driver definition java.util.UUID pg-uuid SerializableType VARBINARY implementors of java.lang.Serializable Unlike the other value types, multiple instances of this type are registered. It is registered once under java.io.Serializable, and registered under the specific java.io.Serializable implementation class names. StringNVarcharType NVARCHAR java.lang.String nstring NTextType LONGNVARCHAR java.lang.String ntext NClobType NCLOB java.sql.NClob nclob, java.sql.NClob MaterializedNClobType NCLOB java.lang.String materialized_nclob PrimitiveCharacterArrayNClobType NCHAR char[] N/A CharacterNCharType NCHAR java.lang.Character ncharacter CharacterArrayNClobType NCLOB java.lang.Character[] N/A Table 2. Java 8 BasicTypes Hibernate type (org.hibernate.type package) JDBC type Java type BasicTypeRegistry key(s) DurationType BIGINT java.time.Duration Duration, java.time.Duration InstantType TIMESTAMP java.time.Instant Instant, java.time.Instant LocalDateTimeType TIMESTAMP java.time.LocalDateTime LocalDateTime, java.time.LocalDateTime LocalDateType DATE java.time.LocalDate LocalDate, java.time.LocalDate LocalTimeType TIME java.time.LocalTime LocalTime, java.time.LocalTime OffsetDateTimeType TIMESTAMP java.time.OffsetDateTime OffsetDateTime, java.time.OffsetDateTime OffsetTimeType TIME java.time.OffsetTime OffsetTime, java.time.OffsetTime OffsetTimeType TIMESTAMP java.time.ZonedDateTime ZonedDateTime, java.time.ZonedDateTime Table 3. Hibernate Spatial BasicTypes
  • 17. Hibernate type (org.hibernate.spatial package) JDBC type Java type BasicTypeRegistry key(s) JTSGeometryType depends on the dialect com.vividsolutions.jts.geom.Geometry jts_geometry, or the classname of Geometry or any of its subclasses GeolatteGeometryType depends on the dialect org.geolatte.geom.Geometry geolatte_geometry, or the classname of Geometry or any of its subclasses
  • 18. Entity 클래스를 영속화하려면 먼저 엔티티로 정의해야 하는데, @Entity 어노테이션으로 정의할 수 있다 또한 xml으로도 지정가능합니다. @Entity package com.khh.hibernate.c1.entity; import lombok.Data; import lombok.extern.slf4j.Slf4j; import javax.persistence.Entity; import javax.persistence.Id; @Data @Entity public class UserNormal { @Id Integer seq; String name; String address; Integer age; } drop table UserNormal if exists create table UserNormal ( seq integer not null, address varchar(255), age integer, name varchar(255), primary key (seq) ) 기본값으로 Entity 클래스 이름이 테이블 이름으로 적용되고 필드 이름이 컬럼 이름으로 생성된다. Table name, Column name 을 직접 지정할수 있다. package com.khh.hibernate.c1.entity; import lombok.Data; import lombok.extern.slf4j.Slf4j; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Data @Entity @Table(name = "USER") public class UserNaming { @Id @Column(name = "JUMIN_NUMBER") Integer seq; @Column(name = "USER_NAME") String name; @Column(name = "USER_ADDR") String address; @Column(name = "USER_AGE") Integer age; } drop table UserNormal if exists create table USER ( JUMIN_NUMBER integer not null, USER_ADDR varchar(255), USER_AGE integer, USER_NAME varchar(255), primary key (JUMIN_NUMBER) )
  • 19. 식별자 각 객체는 유일한 식별자를 가지고 데이터베이스에 영속화되어야 한다. 이때 식별자를 자동으로 생성하는 다양한 방법을 활용할 수 있다. @GeneratedValue 어노테이션을 추가하면 요구 사항에 맞춘 다른 생성 방법을 설정할 수 있다. @GenerateValue 어노테이션은 strategy와 generator 두 가지 속성이 있는데, strategy 속성은 사용할 식별자 생성 타입을 가리키고 generator속성은 식별자를 생성할 메소드를 정의한다. 다음 코드는 ID 생성을 위한 IDENTITY 방법을 보여준다 기본값 : GenerationType.AUTO GenerationType.AUTO 기본 방법으로 다른 데이터베이스 간에도 이용할 수 있다. 하이버네이트에서는 데이터베이스를 기반으로 적절한 ID를 선택한다. GenerationType.IDENTITY 이 설정은 몇몇 데이터베이스에서 제공하는 identity 함수를 기반으로 한다. 데 이터베이스에서 고유한 식별자를 제공하는 역할을 한다. GenerationType.SEQUENCE 몇몇 데이터베이스에서는 연속된 숫자에 관한 메커니즘을 제공하는데, 하이버 네이트에서는 일련번호를 사용한다. GenerationType.TABLE 다른 테이블의 고유한 컬럼 값에서 기본키를 생성하는데, 이 경우 TABLE 생성자 를 사용한다. 시퀀스seqeunce 방법에서는 strategy와 generator 속성을 모두 정의해 야 한다. GenerationType.AUTO @Data @Entity public class UserGen_AUTO { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer seq; String name; String address; Integer age; } create sequence hibernate_sequence start with 1 increment by 1 create table UserGen_AUTO ( seq integer not null, address varchar(255), age integer, name varchar(255), primary key (seq) ) 자동으로 hibernate_sequence를 생성한다. ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserGen_AUTO user = new UserGen_AUTO(); user.setName("name"); user.setAddress("addr"); user.setAge(100); session.save(user); session.getTransaction().commit(); call next value for hibernate_sequence insert into
  • 20. UserGen_AUTO (address, age, name, seq) values (?, ?, ?, ?) binding parameter [1] as [VARCHAR] - [addr] binding parameter [2] as [INTEGER] - [100] binding parameter [3] as [VARCHAR] - [name] binding parameter [4] as [INTEGER] - [1] GenerationType.IDENTITY @Data @Entity public class UserGen_IDENTITY { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String address; Integer age; } create table UserGen_IDENTITY ( seq integer generated by default as identity, address varchar(255), age integer, name varchar(255), primary key (seq) ) ) ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserGen_IDENTITY user = new UserGen_IDENTITY(); user.setName("name"); user.setAddress("addr"); user.setAge(100); session.save(user); session.getTransaction().commit(); insert into UserGen_IDENTITY (seq, address, age, name) values (null, ?, ?, ?) binding parameter [1] as [VARCHAR] - [addr] binding parameter [2] as [INTEGER] - [100] binding parameter [3] as [VARCHAR] - [name] GenerationType.SEQUENCE @Data @Entity @SequenceGenerator(name = UserGen_SEQUENCE.SEQUENCE_GEN_NAME, sequenceName = "EMP_SEQ_GEN") public class UserGen_SEQUENCE { public static final String SEQUENCE_GEN_NAME = "empSeqGen"; @Id @GeneratedValue (strategy = GenerationType.SEQUENCE, generator = SEQUENCE_GEN_NAME) Integer seq; String name; String address; Integer age; } create sequence EMP_SEQ_GEN start with 1 increment by 50 create table UserGen_SEQUENCE ( seq integer not null, address varchar(255), age integer, name varchar(255), primary key (seq) ) ▼실행
  • 21. Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserGen_SEQUENCE user = new UserGen_SEQUENCE(); user.setName("name"); user.setAddress("addr"); user.setAge(100); session.save(user); session.getTransaction().commit(); call next value for EMP_SEQ_GEN insert into UserGen_SEQUENCE (address, age, name, seq) values (?, ?, ?, ?) 15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [addr] 15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - [100] 15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [name] 15:18 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - [1] GenerationType.TABLE @Data @Entity @TableGenerator(name = UserGen_TABLE.TABLE_GEN_NAME, table = "USER_ID_TABLE") public class UserGen_TABLE { public static final String TABLE_GEN_NAME = "empTableGen"; @Id @GeneratedValue (strategy = GenerationType.TABLE, generator = TABLE_GEN_NAME) Integer seq; String name; String address; Integer age; } create table USER_ID_TABLE ( sequence_name varchar(255) not null, next_val bigint, primary key (sequence_name) ) create table UserGen_TABLE ( seq integer not null, address varchar(255), age integer, name varchar(255), primary key (seq) ) ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserGen_TABLE user = new UserGen_TABLE(); user.setName("name"); session.save(user); session.getTransaction().commit(); select tbl.next_val from USER_ID_TABLE tbl where tbl.sequence_name=? for update insert into USER_ID_TABLE
  • 22. (sequence_name, next_val) values (?,?) update USER_ID_TABLE set next_val=? where next_val=? and sequence_name=? select tbl.next_val from USER_ID_TABLE tbl where tbl.sequence_name=? for update update USER_ID_TABLE set next_val=? where next_val=? and sequence_name=? insert into UserGen_TABLE (address, age, name, seq) values (?, ?, ?, ?) 13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - [null] 13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - [null] 13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [name] 13:35 TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - [1] 복합 식별자 ID (KEY) 복합 아이디composite-id 식별자 설정과 관련된 세 가지 방법 1. @Embededdable 2. @EmbeddedId 3. @IdClass ID 값으로 사용되는 클래스에서 구현해야될것들 1. Default Constructor() 2. hashCode() 3. equals() 4. implements Serializable tip : Lombok lib 사용한다면 위 내용을 자동으로 생성해준다.( https://projectlombok.org/ )
  • 23. 키선언 첫번째 방법 (@Embeddable) @Data @Embeddable public class UserPK_Embededdable implements Serializable{ String name; Integer number; } @Data @Entity public class UserInfo{ @Id UserPK_Embededdable id; String address; Integer age; } create table UserInfo ( name varchar(255) not null, number integer not n ull, address varchar(255), age integer, primary key (name, number) ) Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserPK_Embededdable pk = new UserPK_Embededdable(); pk.setName("name"); pk.setNumber(128); UserInfo_Embededdable user = new UserInfo_Embededdable(); user.setId(pk); user.setAddress("korea"); user.setAge(53); session.save(user); session.getTransaction().commit(); insert into UserInfo_Embededdable (address, age, name, number) values (?, ?, ?, ?) binding parameter [1] as [VARCHAR] - [korea] binding parameter [2] as [INTEGER] - [53] binding parameter [3] as [VARCHAR] - [name] binding parameter [4] as [INTEGER] - [128] //select UserPK_Embededdable userPK = new UserPK_Embededdable(); UserPK_IdClass(); userPK.setName("name"); userPK.setNumber(128); UserInfo_Embededdable userBydb = session.get(UserInfo_Embededdable.class,userPK ); log.info("get::"+userBydb.toString()); select userinfo_e0_.name as name1_1_0_, userinfo_e0_.number as number2_1_0_, userinfo_e0_.address as address3_1_0_, userinfo_e0_.age as age4_1_0_ from UserInfo_Embededdable userinfo_e0_ where userinfo_e0_.name=? and userinfo_e0_.number=? binding parameter [1] as [VARCHAR] - [name] binding parameter [2] as [INTEGER] - [128] get::UserInfo_Embededdable(id=UserPK_Embededdable(name=name, number=128), address=korea, age=53) tip : @Embeddable 을 선언하지 않고 @Id 로 사용한다면 해당클래스의 toString()값이 들어간다
  • 24. 키선언 두번째 방법 (@EmbeddedId) @Data public class UserPK_EmbeddedId implements Serializable{ String name; Integer number; } @Data @Entity public class UserInfo_EmbeddedId { @EmbeddedId UserPK_Embededdable id; String address; Integer age; } create table UserInfo_EmbeddedId ( name varchar(255) not null, number integer not null, address varchar(255), age integer, primary key (name, number) ) Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserPK_EmbeddedId pk = new UserPK_EmbeddedId(); pk.setName("name"); pk.setNumber(128); UserInfo_EmbeddedId user = new UserInfo_EmbeddedId(); user.setId(pk); user.setAddress("korea"); user.setAge(53); session.save(user); session.getTransaction().commit(); insert into UserInfo_EmbeddedId (address, age, name, number) values (?, ?, ?, ?) binding parameter [1] as [VARCHAR] - [korea] binding parameter [2] as [INTEGER] - [53] binding parameter [3] as [VARCHAR] - [name] binding parameter [4] as [INTEGER] - [128] //select UserPK_EmbeddedId userPK = new UserPK_EmbeddedId(); userPK.setName("name"); userPK.setNumber(128); UserInfo_EmbeddedId userBydb = session.get(UserInfo_EmbeddedId.class,userPK) ; log.info("get::"+userBydb.toString()); select userinfo_e0_.name as name1_0_0_, userinfo_e0_.number as number2_0_0_, userinfo_e0_.address as address3_0_0_, userinfo_e0_.age as age4_0_0_ from UserInfo_EmbeddedId userinfo_e0_ where userinfo_e0_.name=? and userinfo_e0_.number=? binding parameter [1] as [VARCHAR] - [name] binding parameter [2] as [INTEGER] - [128] get::UserInfo_EmbeddedId(id=UserPK_EmbeddedId(name=name, number=128), address=korea, age=53)
  • 25. 키선언 세번째 방법 (@IdClass) @Data public class UserPK_IdClass implements Serializable{ String name; Integer number; } @Data @Entity @IdClass(UserPK_IdClass.class) public class UserInfo_IdClass { @Id String name; @Id Integer number; String address; Integer age; } create table UserInfo_IdClass ( name varchar(255) not null, number integer not null, address varchar(255), age integer, primary key (name, number) ) ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserInfo_IdClass pk = new UserInfo_IdClass(); pk.setName("name"); pk.setNumber(128); UserInfo_IdClass user = pk; user.setAddress("korea"); user.setAge(53); session.save(user); session.getTransaction().commit(); insert into UserInfo_IdClass (address, age, name, number) values (?, ?, ?, ?) binding parameter [1] as [VARCHAR] - [korea] binding parameter [2] as [INTEGER] - [53] binding parameter [3] as [VARCHAR] - [name] binding parameter [4] as [INTEGER] - [128] UserPK_IdClass userPK = new UserPK_IdClass(); userPK.setName("name"); userPK.setNumber(128); UserInfo_IdClass userBydb = session.get(UserInfo_IdClass.class,userPK); log.info("get::"+userBydb.toString()); select userinfo_i0_.name as name1_2_0_, userinfo_i0_.number as number2_2_0_, userinfo_i0_.address as address3_2_0_, userinfo_i0_.age as age4_2_0_ from UserInfo_IdClass userinfo_i0_ where userinfo_i0_.name=? and userinfo_i0_.number=? binding parameter [1] as [VARCHAR] - [name] binding parameter [2] as [INTEGER] - [128] get::UserInfo_IdClass(name=name, number=128, address=korea, age=53)
  • 26. Join 조인 연관 관계 객체 영속화 세상에서는 연관 관계 assocation 와 관계 relationship 에 대한 이해는 필수입니다. 연관 관계에서 반드시 기억할 두 가지는 다중성 multiplicity 과 방향성 directionality 입니다 다중성 일대일 1:1 한 테이블에서 각 레코드는 반드시 다른 테이블 의 레코드 한 개와 관계가 있다. 반대의 경우도 마찬가지다. 다른 테이블의 레코드는 0 일 수도 있다. 자동차 한 대는 오직 한 개의 엔진만 가진다. 일대다 또는 다대일 1:N, N:1 한 테이블에서 각 레코드는 다른 테이블의 0 개 또는 그 이상의 레코드와 관계가 있다. 영화 한 편은 많은 배우를 가진다(일대다) 배우 한 명은 여러 작품에서 연기할 수 있다 (다대일). N:N 양쪽 테이블 모두 각 레코드가 다른 쪽 테이블의 0 개 또는 그 이상의 레코드와 관계가 있다. 방향성 Car 와 Engine 의 관계에서 Car 의 속성을 질의해서 Engine 을 찾아낼수있습니다. car -> engin Car 클래스와 Owner 클래스의 경우 주어진 Car 객체로 자동차의 주인이 누구인지 알 수 있으며, Owner 객체로 차주의 자동차가 무엇인지 알 수 있습니다 양방향성 연관 관계를 유지할 수 있도록 Owner 객체에 Car 에 대한 참조를 제공하 고, Car 객체에는 Owner 에 대한 참조를 제공하고 있습니다. 조인전에 먼저 알아야될 Cascade 부모 객체와 자식 객체의 종속성 설정 : 하이버네이트에서는 "부모"객체가 실행되면 "자식" 혹은 "의존" 객체까지 전이되는 연산을 cascade 어트리뷰트로 처리할 수 있다. 이 기능은 모든 종류의 컬렉션과 연관에 적용된다. 타입 행위 언제 CascadeType.DETACH 엔티티가 Persistence Context 에서 제거되면 (엔티티가 분리 될 것입니다)이 작업은 관계에 반영됩니다. Finished Persistence Context 또는 entityManager.detach () entityManager.clear () CascadeType.MERGE 엔티티에 업데이트 된 데이터가 있으면이 작업이 관계에 반영됩니다 엔티티가 갱신되고 트랜잭션이 완료되거나, entityManager.merge () CascadeType.PERSIST 새로운 엔티티가 데이터베이스에 유지되면이 조치가 관계에 반영됩니다. 트랜잭션이 끝나거나, entityManager.persist () CascadeType.REFRESH 엔티티가 데이터베이스와 동기화 된 데이터를 가질 때이 조치가 반영됩니다 entityManager.refresh () CascadeType.REMOVE 엔터티가 데이터베이스에서 삭제되면 entityManager.remove ()
  • 27. 행동이 관계에 반영 될 것입니다. CascadeType.ALL 위의 조치 중 하나가 JPA 또는 명령에 의해 호출 될 때,이 조치는 관계에 반영됩니다. 위에 설명 된 명령이나 행동. 쉽게 말하기 : 객체 상태 전이 타입 (보통 ALL 을 사용한다) @OneToOne 1. FK 지정 @Data @Entity public class User_OneToOne { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String password; @OneToOne(cascade = CascadeType.ALL) UserInfo info; } @Data @Entity public class UserInfo { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer seq; String addr; double weight; double height; } create table UserInfo ( seq integer not null, addr varchar(255), height double not null, weight double not null, primary key (seq) ) create table User_OneToOne ( seq integer generated by default as identity, name varchar(255), password varchar(255), info_seq integer, primary key (seq) ) alter table User_OneToOne add constraint FKmn264qa7ngjx89j3u9a2l7ql2 foreign key (info_seq) references UserInfo 집고 넘어가기 : 자동으로 User_OneToOne Table(부모테이블)에 INFO_SEQ컬럼 생기고 FK를건다 ▼실행
  • 28. Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserInfo info = new UserInfo(); info.setAddr("addr"); info.setHeight(180); info.setWeight(70); User_OneToOne user = new User_OneToOne(); user.setName("name"); user.setPassword("pwd"); user.setInfo(info); session.save(user); session.getTransaction().commit(); insert into UserInfo (addr, height, weight, seq) values (?, ?, ?, ?) binding parameter [1] as [VARCHAR] - [addr] binding parameter [2] as [DOUBLE] - [180.0] binding parameter [3] as [DOUBLE] - [70.0] binding parameter [4] as [INTEGER] - [1] insert into User_OneToOne (seq, info_seq, name, password) values (null, ?, ?, ?) binding parameter [1] as [INTEGER] - [1] binding parameter [2] as [VARCHAR] - [name] binding parameter [3] as [VARCHAR] - [pwd]
  • 29. 1. PK 를 FK 지정 (아주 중요함) @Data @Entity @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = SEQ_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName="SEQ") // @PrimaryKeyJoinColumn(name = "USER_SEQ", referencedColumnName="SEQ") UserInfo info; } @Data @Entity @GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user")) public class UserInfo { @Id @Column(name = "SEQ") @GeneratedValue(generator = "generator") Integer seq; String addr; double weight; double height; @OneToOne(cascade = CascadeType.ALL, mappedBy = "info") User user; } create table User ( USER_SEQ integer not null, name varchar(255), password varchar(255), primary key (USER_SEQ) ) create table UserInfo ( SEQ integer not null, addr varchar(255), height double not null, weight double not null, primary key (SEQ) ) ▼ 실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); call next value for SEQ_USER
  • 30. UserInfo info = new UserInfo(); info.setAddr("addr"); info.setHeight(180); info.setWeight(70); User user = new User(); user.setName("name"); user.setPassword("pwd"); //서로의 관계를 맺어준다. user.setInfo(info); info.setUser(user); session.save(user); session.flush(); session.clear(); UserInfo userInfoBydb = session.get(UserInfo.class,100); if(null!=userInfoBydb && null!=userInfoBydb.getUser()) log.debug("get(DB) Entity ---> "+userInfoBydb.getUser().getName()); session.getTransaction().commit(); Sequence value obtained: 100 insert into User (name, password, USER_SEQ) values (?, ?, ?) [1] as [VARCHAR] - [name] [2] as [VARCHAR] - [pwd] [3] as [INTEGER] - [100] insert into UserInfo (addr, height, weight, SEQ) values (?, ?, ?, ?) [1] as [VARCHAR] - [addr] [2] as [DOUBLE] - [180.0] [3] as [DOUBLE] - [70.0] [4] as [INTEGER] - [100] select userinfo0_.SEQ as SEQ1_1_0_, userinfo0_.addr as addr2_1_0_, userinfo0_.height as height3_1_0_, userinfo0_.weight as weight4_1_0_, user1_.USER_SEQ as USER_SEQ1_0_1_, user1_.name as name2_0_1_, user1_.password as password3_0_1_ from UserInfo userinfo0_ left outer join User user1_ on userinfo0_.SEQ=user1_.USER_SEQ where userinfo0_.SEQ=? [1] as [INTEGER] - [100] get(DB) Entity ---> name 자동으로 부모 테이블의 PK값을 가지고 자신의 FK쪽 값으로 사용한다 @GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user")) .... @GeneratedValue(generator = "generator") Integer seq; value값은 부모Entity객체 필드명 집고 넘어가기 : @JoinColumn(name = "USER_SEQ", referencedColumnName="SEQ") name = 자기 자신 테이블의 FK대상 컬럼명
  • 31. referencedColumnName = 자식테이블 컬럼명 (안적으면 자동으로 자식테이블 ID 로 해준다.) 또한 PrimaryKeyJoinColumn 을 쓸수도 있다 @PrimaryKeyJoinColumn(name = "USER_SEQ", referencedColumnName="SEQ") 방향성을 갖자 소유자 찾기 @Data @Entity @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = SEQ_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToOne(cascade = CascadeType.ALL) UserInfo info; } @Data @Entity @GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user")) public class UserInfo { @Id @Column(name = "SEQ") @GeneratedValue(generator = "generator") Integer seq; String addr; double weight; double height; @OneToOne(cascade = CascadeType.ALL, mappedBy = "info") User user; } UserInfo Entity (자식테이블) 에서 부모Entity에서 선언된 자식Entity의 필드명을 적으면된다 @OneToOne(mappedBy = “code feildName”) 집고 넘어가기 : insert 했을때 Entity 는 관계설정이 되어있지 않아 로그가 찍히지 않는다 하지만 다시 SELECT 해온 Entity 는 방향성 설정이 되어 넘어온다 여기서 알수 있는것은 insert 했을때 사용했던 Entity 와 get 해온 Entity 는 다르다는것을 알수 있다.
  • 32. @OneToMany, @ManyToOne 1:N, N:1, N:N 컬렉션 영속화 (List, Set, Map, Array[]) List ▼ OneToMany 조인하기 맵핑 테이블 만들어 조인하기 @Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) List<Auth> auths = null; } @Data @Entity @SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName = Auth.SEQUENCE_NAME, initialValue = 100) public class Auth implements Serializable { public static final String SEQUENCE_NAME = "AUTH_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) Integer seq; String auth; Date expiry; } create table Auth ( seq integer not null, auth varchar(255), expiry timestamp, primary key (seq) ) create table User ( seq integer generated by default as identity, name varchar(255), password varchar(255), primary key (seq) ) -- 새로운 맵핑 테이블이 생긴다 create table User_Auth ( User_seq integer not null, auths_seq integer not null ) alter table User_Auth add constraint UK_n1upof81pc3xys7s3xrovghof unique (auths_seq) alter table User_Auth add constraint FKmnns1wp8mo27pxkbbwjl8nuun foreign key (auths_seq) references Auth
  • 33. alter table User_Auth add constraint FKn1dj4srm089e409sq8vkhogug foreign key (User_seq) references User ▼맵핑 테이블 이름바꾸기 @Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinTable(name = "USER_AUTH_MAPPING") List<Auth> auths = null; } ▼실행
  • 34. Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); Auth auth1 = new Auth(); auth1.setAuth("ROLE_ADMIB"); auth1.setExpiry(new Date()); Auth auth2 = new Auth(); auth2.setAuth("ROLE_USER"); auth2.setExpiry(new Date()); User user = new User(); user.setAuths(Arrays.asList(auth1,auth2)); user.setName("name"); user.setPassword("pwd"); session.save(user); session.getTransaction().commit(); insert into Auth (auth, expiry, seq) values (?, ?, ?) binding parameter [1] as [VARCHAR] - [ROLE_ADMIB] binding parameter [2] as [TIMESTAMP] - [Sat Dec 17 14:27:21 KST 2016] binding parameter [3] as [INTEGER] - [100] insert into Auth (auth, expiry, seq) values (?, ?, ?) binding parameter [1] as [VARCHAR] - [ROLE_USER] binding parameter [2] as [TIMESTAMP] - [Sat Dec 17 14:27:21 KST 2016] binding parameter [3] as [INTEGER] - [101] insert into User_Auth (User_seq, auths_seq) values (?, ?) binding parameter [1] as [INTEGER] - [1] binding parameter [2] as [INTEGER] - [100] insert into User_Auth (User_seq, auths_seq) values (?, ?) binding parameter [1] as [INTEGER] - [1] binding parameter [2] as [INTEGER] - [101]
  • 35. Array @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ") @OrderColumn private AuthComposite[] auths; } user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).toArray(AuthComposite[]::new)); 집고 넘어가기 : Array사용할때에는 @OrderColumn으로 선언해야된다. @OrderColumn은 Index값이 들어가도록 하는것입니다.
  • 36. Set @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ") private Set<AuthComposite> auths; } user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).collect(Collectors.toSet())); Map @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ") private Map<String,AuthComposite> auths; } user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}). collect(Collectors.toMap(c-> c.getAuth(),c->c)));
  • 37. ▼바로 OneToMany 조인하기(맵핑테이블 없음) (자식테이블 인조식별자 + 유니크 인덱스) @Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn List<Auth> auths = null; } @Data @Entity @SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName = Auth.SEQUENCE_NAME, initialValue = 100) public class Auth implements Serializable { public static final String SEQUENCE_NAME = "AUTH_SEQ"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String auth; Date expiry; } ▼자식테이블 FK 컬럼 명바꾸기 Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ") List<Auth> auths = null; } 집고 넘어가기 : OneToOne에서는 자기자신 컬럼을 가르켰지만 OneToMany에서는 자식컬럼을 말한다.
  • 38. ▼PK 를 FK 지정 (맵핑 테이블 없음) @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "SEQ") List<Auth> auths = null; } @Data @Entity @SequenceGenerator(name = Auth.SEQUENCE_NAME, sequenceName = Auth.SEQUENCE_NAME, initialValue = 100) public class Auth implements Serializable { public static final String SEQUENCE_NAME = "AUTH_SEQ"; @Id @Column(name = "USER_SEQ") Integer seq; @Id String auth; Date expiry; } ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); Auth auth1 = new Auth(); auth1.setAuth("ROLE_ADMIB"); auth1.setExpiry(new Date()); Auth auth2 = new Auth(); auth2.setAuth("ROLE_USER"); auth2.setExpiry(new Date()); User user = new User(); user.setSeq(50); user.setAuths(Stream.of(auth1,auth2).peek(it- >it.setSeq(user.getSeq())).collect(Collectors.toList())); user.setName("name"); user.setPassword("pwd"); session.save(user); session.getTransaction().commit(); 집고 넘어가기 : (PK를 FK지정) (맵핑 테이블 없음) 는 좀 문제가 있다. 수동으로 KEY값을 맵핑시켜줘다. 자동으로 맵핑할수 있는 방법도 있다. (아래에서 알아보자) @GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user")) http://howtodoinjava.com/hibernate/hibernate-one-to-many-mapping-using-annotations/ https://en.wikibooks.org/wiki/Java_Persistence/OneToMany http://www.beingjavaguys.com/2013/09/hibernate-one-to-many-mapping.html http://levelup.lishman.com/hibernate/associations/one-to-many.php ▼PK 를 FK 지정 (맵핑 테이블 없음) GeneratedValue 값으로 KEY 값 처리시 문제. @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME)
  • 39. @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ") private List<AuthComposite> auths; } @Data @Entity @SequenceGenerator(name = AuthComposite.SEQUENCE_NAME, sequenceName = AuthComposite.SEQUENCE_NAME, initialValue = 50) public class AuthComposite implements Serializable{ public static final String SEQUENCE_NAME = "AUTHCOMPOSITE_SEQ"; @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Id @Column(name = "USER_SEQ",nullable = false) Integer seq; @Id @Column(nullable = false) String auth; Date expiry; } insert into User (name, password, USER_SEQ) values (?, ?, ?) 12:08:37 TRACE : binding parameter [1] as [VARCHAR] - [name] 12:08:37 TRACE : binding parameter [2] as [VARCHAR] - [pwd] 12:08:37 TRACE : binding parameter [3] as [INTEGER] - [100] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) 12:08:37 TRACE : binding parameter [1] as [TIMESTAMP] - [Sun Dec 18 12:08:36 KST 2016] 12:08:37 TRACE : binding parameter [2] as [INTEGER] - [50] 12:08:37 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_ADMIN] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) 12:08:37 TRACE : binding parameter [1] as [TIMESTAMP] - [Sun Dec 18 12:08:36 KST 2016] 12:08:37 TRACE : binding parameter [2] as [INTEGER] - [51] 12:08:37 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_USER] ERROR : HHH000315: Exception executing batch [org.h2.jdbc.JdbcBatchUpdateException: Referential integrity constraint violation: "FKOU1XVFC0SWVOFR46KFD8KHY1N: PUBLIC.AUTHCOMPOSITE FOREIGN KEY(USER_SEQ) REFERENCES PUBLIC.USER(USER_SEQ) (51)"; SQL statement:
  • 40. AuthComposite authc1 = new AuthComposite(); authc1.setAuth("ROLE_ADMIN"); authc1.setExpiry(new Date()); AuthComposite authc2 = new AuthComposite(); authc2.setAuth("ROLE_USER"); authc2.setExpiry(new Date()); User user = new User(); user.setAuths(Stream.of(authc1,authc2).collect(Collectors.toList())); user.setName("name"); user.setPassword("pwd"); log.debug("---->"+user.getSeq()); session.save(user); 집고 넘어가기 : 부모Entity와 자식Entity에 관계가 맺어있지만 각각클래스의 ID값에 GeneratedValue 설정했기떄문 에 각각 시퀀스 값으로 들어간다 여기서 FK제약조건이 걸려 오류가 난것이다. save 할때 어떻게 해야될까? 1. 이렇게 해보자 부모Entity는 ID는 GeneratedValue 자식Entity은 GeneratedValue사용하지 않고 자동으로 넘어가도록 기대해보자 @Data @Entity public class AuthComposite implements Serializable{ @Id @Column(name = "USER_SEQ",nullable = false) Integer seq; @Id @Column(nullable = false) String auth; Date expiry; } insert into User (name, password, USER_SEQ) values (?, ?, ?) parameter [1] as [VARCHAR] - [name] parameter [2] as [VARCHAR] - [pwd] parameter [3] as [INTEGER] - [100] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:10:36 KST 2016] parameter [2] as [INTEGER] - [null] parameter [3] as [VARCHAR] - [ROLE_ADMIN] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:10:36 KST 2016] parameter [2] as [INTEGER] - [null] parameter [3] as [VARCHAR] - [ROLE_USER] 기대는 했지만 자동으로 넘어가지 않는다. 그저 NULL값이 들어간다 정직하다.. 오류난다
  • 41. 2. 이렇게 해보자 (나눠서 save하자) 부모Entity만 save 선행후~ 부모Entity에서 추출된 ID (SEQUENCE) 값을 가지고 자식Entity ID값으로 지정 그뒤 자식Entity를 save해보자 AuthComposite authc1 = new AuthComposite(); authc1.setAuth("ROLE_ADMIN"); authc1.setExpiry(new Date()); AuthComposite authc2 = new AuthComposite(); authc2.setAuth("ROLE_USER"); authc2.setExpiry(new Date()); User user = new User(); user.setAuths(Stream.of(authc1,authc2).collect(Collectors.toList())); user.setName("name"); user.setPassword("pwd"); log.debug("---->"+user.getSeq()); session.save(user); log.debug("---->"+user.getSeq()); user.getAuths().forEach(it->it.setSeq(user.getSeq())); ---->null call next value for USER_SEQ Sequence value obtained: 100 HHH000387: ResultSet's statement was not registered Generated identifier: 100, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator ---->100 insert into User (name, password, USER_SEQ) values (?, ?, ?) parameter [1] as [VARCHAR] - [name] parameter [2] as [VARCHAR] - [pwd] parameter [3] as [INTEGER] - [100] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:21:49 KST 2016] parameter [2] as [INTEGER] - [100] parameter [3] as [VARCHAR] - [ROLE_ADMIN] insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) parameter [1] as [TIMESTAMP] - [Sun Dec 18 13:21:49 KST 2016] parameter [2] as [INTEGER] - [100] parameter [3] as [VARCHAR] - [ROLE_USER] 집고 넘어가기 1 : 부모Entity만 save후 자식Entity List를 넣은뒤 다시 save했다 여기서 알수 있듯 save 를 타게되면 바로 시퀀스 값을 가져와 바인딩 시켜주니 그것을 가지고 자식 셋팅후
  • 42. commit을 하면 그때 가서 자식 Entity에 바인딩된 ID값으로 insert된다. 집고 넘어가기 2 : PK를 PK복합키로만 구성하는 방법과, 인조식별자+유니크 인덱스로 구성하는것은 아직도 많은 논쟁있다. 하지만 어느정도 대세는 인조식별자+유니크 를 두는것으로 많이 기울었습니다. http://okky.kr/article/257331 3. 이렇게 해보자 (자식Entity에 ID값을 부모Entity의 ID값으로 자동 save하자) 베스트! @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100, allocationSize = 1) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "USER_SEQ") private List<AuthComposite> auths; } @Data @Entity @GenericGenerator(name = "generator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user")) public class AuthComposite implements Serializable{ @Id @Column(name = "USER_SEQ",nullable = false) @GeneratedValue(generator = "generator") Integer seq; @Id @Column(nullable = false) String auth; Date expiry; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name="USER_SEQ", nullable=false,updatable=false,insertable=false) User user; } AuthComposite authc1 = new AuthComposite(); authc1.setAuth("ROLE_ADMIN"); authc1.setExpiry(new Date()); AuthComposite authc2 = new AuthComposite(); authc2.setAuth("ROLE_USER"); authc2.setExpiry(new Date()); User user = new User(); user.setAuths(Stream.of(authc1,authc2).peek(it->{it.setUser(user);}).collect(Collectors.toList())); user.setName("name"); user.setPassword("pwd"); Serializable id = session.save(user); insert into User (name, password, USER_SEQ) values (?, ?, ?) 10:44:27 TRACE : binding parameter [1] as [VARCHAR] - [name] 10:44:27 TRACE : binding parameter [2] as [VARCHAR] - [pwd] 10:44:27 TRACE : binding parameter [3] as [INTEGER] - [100] 10:44:27 DEBUG : Executing batch size: 1 10:44:27 DEBUG : insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) 10:44:27 TRACE : binding parameter [1] as [TIMESTAMP] - [Wed Dec 21 10:44:27 KST 2016] 10:44:27 TRACE : binding parameter [2] as [INTEGER] - [100]
  • 43. 10:44:27 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_ADMIN] 10:44:27 DEBUG : Reusing batch statement 10:44:27 DEBUG : insert into AuthComposite (expiry, USER_SEQ, auth) values (?, ?, ?) 10:44:27 TRACE : binding parameter [1] as [TIMESTAMP] - [Wed Dec 21 10:44:27 KST 2016] 10:44:27 TRACE : binding parameter [2] as [INTEGER] - [100] 10:44:27 TRACE : binding parameter [3] as [VARCHAR] - [ROLE_USER] 객체간 관계만 맺어주면 (변수 값셋팅) 자동으로 부모Entity값을 가지고 자기자신ID값으로 사용하는걸 알수 있다. https://www.mkyong.com/hibernate/hibernate-one-to-one-relationship-example-annotation/ http://www.codejava.net/frameworks/hibernate/hibernate-one-to-one-association-on-primary-key-annotations- example 방향성 갖자 집고 넘어가기 1 : @MonyToOne 쪽에 @JoinColums 를 사용하여 자기자신 테이블 컬럼명을 지정할수도있다. @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name="USER_SEQ", nullable=false,updatable=false,insertable=false) User user; 조회하기 지연로딩 FetchType.LAZY (기본값) @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "USER_SEQ") List<Auth> auths = null; } Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); //////Fetch User userBydb = session.get(User.class,100); //부모테이블만 조회 try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} //아래에서 사용할때 자식테이블 조회 userBydb.getAuths().forEach(it->log.info("get(DB)"+it.getUser().getName())); session.getTransaction().commit();
  • 44. 18:04:14 DEBUG : select user0_.seq as seq1_1_0_, user0_.name as name2_1_0_, user0_.password as password3_1_0_ from User user0_ where user0_.seq=? 18:04:14 TRACE : binding parameter [1] as [INTEGER] - [100] 18:04:19 DEBUG : select auths0_.USER_SEQ as USER_SEQ4_0_0_, auths0_.AUTH_SEQ as AUTH_SEQ1_0_0_, auths0_.auth as auth2_0_0_, auths0_.AUTH_SEQ as AUTH_SEQ1_0_1_, auths0_.auth as auth2_0_1_, auths0_.expiry as expiry3_0_1_, auths0_.user_seq as user_seq4_0_1_, user1_.seq as seq1_1_2_, user1_.name as name2_1_2_, user1_.password as password3_1_2_ from Auth auths0_ left outer join User user1_ on auths0_.user_seq=user1_.seq where auths0_.USER_SEQ=? 18:04:19 TRACE : binding parameter [1] as [INTEGER] - [100] 즉시로딩 FetchType.EAGER @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) Integer seq; String name; String password; @OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) @JoinColumn(name = "USER_SEQ") List<Auth> auths = null; } 즉시 조인 쿼리가 날라간다. select user0_.seq as seq1_1_0_, user0_.name as name2_1_0_, user0_.password as password3_1_0_, auths1_.USER_SEQ as USER_SEQ4_0_1_,
  • 45. auths1_.AUTH_SEQ as AUTH_SEQ1_0_1_, auths1_.auth as auth2_0_1_, auths1_.AUTH_SEQ as AUTH_SEQ1_0_2_, auths1_.auth as auth2_0_2_, auths1_.expiry as expiry3_0_2_, auths1_.user_seq as user_seq4_0_2_, user2_.seq as seq1_1_3_, user2_.name as name2_1_3_, user2_.password as password3_1_3_ from User user0_ left outer join Auth auths1_ on user0_.seq=auths1_.USER_SEQ left outer join User user2_ on auths1_.user_seq=user2_.seq where user0_.seq=? 18:09:42 TRACE : binding parameter [1] as [INTEGER] - [100]
  • 46. @ManyToMany @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; @ManyToMany(cascade = CascadeType.ALL) @JoinColumn(name = "USER_SEQ", referencedColumnName = "SHOP_SEQ", nullable = false) List<CoffeShop> shops = null; } @Data @Entity //@IdClass(CoffeShop.class) @SequenceGenerator(name = CoffeShop.SEQUENCE_NAME, sequenceName = CoffeShop.SEQUENCE_NAME, initialValue = 100) public class CoffeShop implements Serializable { public static final String SEQUENCE_NAME = "SHOP_SEQ"; @Id @Column(name = "SHOP_SEQ") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) Integer seq; String number; Date open; Date close; @ManyToMany(cascade = CascadeType.ALL,mappedBy = "shops") List<User> users = null; } Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); User user1 = new User(); user1.setName("name1"); User user2 = new User(); user2.setName("name2"); CoffeShop shop1 = new CoffeShop(); shop1.setName("shop1"); CoffeShop shop2 = new CoffeShop(); shop2.setName("shop2"); user1.setShops(Arrays.asList(shop1,shop2)); user2.setShops(Arrays.asList(shop1,shop2)); session.save(user1); session.save(user2); session.getTransaction().commit(); insert into User (name, USER_SEQ) values
  • 47. (?, ?) 15:53:09 TRACE : binding parameter [1] as [VARCHAR] - [name1] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100] insert into CoffeShop (close, name, open, SHOP_SEQ) values (?, ?, ?, ?) 15:53:09 TRACE : binding parameter [1] as [TIMESTAMP] - [null] 15:53:09 TRACE : binding parameter [2] as [VARCHAR] - [shop1] 15:53:09 TRACE : binding parameter [3] as [TIMESTAMP] - [null] 15:53:09 TRACE : binding parameter [4] as [INTEGER] - [100] insert into CoffeShop (close, name, open, SHOP_SEQ) values (?, ?, ?, ?) 15:53:09 TRACE : binding parameter [1] as [TIMESTAMP] - [null] 15:53:09 TRACE : binding parameter [2] as [VARCHAR] - [shop2] 15:53:09 TRACE : binding parameter [3] as [TIMESTAMP] - [null] 15:53:09 TRACE : binding parameter [4] as [INTEGER] - [101] insert into User (name, USER_SEQ) values (?, ?) 15:53:09 TRACE : binding parameter [1] as [VARCHAR] - [name2] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101] 15:53:09 DEBUG : Executing batch size: 1 insert into User_CoffeShop (users_USER_SEQ, shops_SHOP_SEQ) values (?, ?) 15:53:09 TRACE : binding parameter [1] as [INTEGER] - [100] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100] insert into User_CoffeShop (users_USER_SEQ, shops_SHOP_SEQ) values (?, ?) 15:53:09 TRACE : binding parameter [1] as [INTEGER] - [100] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101] 15:53:09 DEBUG : Done inserting collection: 2 rows inserted 15:53:09 DEBUG : Inserting collection: [com.khh.hibernate.c2.join.manytomany.entity.User.shops#101] insert into
  • 48. User_CoffeShop (users_USER_SEQ, shops_SHOP_SEQ) values (?, ?) 15:53:09 TRACE : binding parameter [1] as [INTEGER] - [101] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [100] insert into User_CoffeShop (users_USER_SEQ, shops_SHOP_SEQ) values (?, ?) 15:53:09 TRACE : binding parameter [1] as [INTEGER] - [101] 15:53:09 TRACE : binding parameter [2] as [INTEGER] - [101] CoffeShop shopBydb = session.get(CoffeShop.class,100); shopBydb.getUsers().forEach(it->log.debug("-->"+it.getName())); session.getTransaction().commit(); select users0_.shops_SHOP_SEQ as shops_SH2_2_0_, users0_.users_USER_SEQ as users_US1_2_0_, user1_.USER_SEQ as USER_SEQ1_1_1_, user1_.name as name2_1_1_ from User_CoffeShop users0_ inner join User user1_ on users0_.users_USER_SEQ=user1_.USER_SEQ where users0_.shops_SHOP_SEQ=? 15:59:43 TRACE : binding parameter [1] as [INTEGER] - [100] 15:59:43 DEBUG : -->name1 15:59:43 DEBUG : -->name2
  • 49. N:N 연결하기 @Data @Entity @SequenceGenerator(name = User.SEQUENCE_NAME, sequenceName = User.SEQUENCE_NAME, initialValue = 100) public class User { public static final String SEQUENCE_NAME = "USER_SEQ"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) @Column(name = "USER_SEQ") Integer seq; String name; @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") List<UserShop> shops = null; } @Entity @SequenceGenerator(name = CoffeShop.SEQUENCE_NAME, sequenceName = CoffeShop.SEQUENCE_NAME, initialValue = 100) public class CoffeShop implements Serializable { public static final String SEQUENCE_NAME = "SHOP_SEQ"; @Id @Column(name = "SHOP_SEQ") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE_NAME) Integer seq; String name; Date open; Date close; @OneToMany(cascade = CascadeType.ALL, mappedBy = "shop") List<UserShop> shops = null; } @Data public class UserShopPK implements Serializable { @Column(name = "USER_SEQ") Integer user_seq; @Column(name = "SHOP_SEQ") Integer shop_seq; public UserShopPK() {} public UserShopPK(Integer user_seq, Integer shop_seq){ this.user_seq = user_seq; this.shop_seq = shop_seq; } }
  • 50. @Data @Entity @IdClass(UserShopPK.class) public class UserShop{ @Id Integer user_seq; @Id Integer shop_seq; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name="USER_SEQ", referencedColumnName = "USER_SEQ", insertable = false, updatable = false) private User user; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name="SHOP_SEQ",referencedColumnName = "SHOP_SEQ", insertable = false, updatable = false) private CoffeShop shop; @Temporal(TemporalType.TIMESTAMP) // @ColumnDefault("now()") @Version Date reg; } create table CoffeShop ( SHOP_SEQ integer not null, close timestamp, name varchar(255), open timestamp, primary key (SHOP_SEQ) ) create table User ( USER_SEQ integer not null, name varchar(255), primary key (USER_SEQ) ) create table UserShop ( SHOP_SEQ integer not null, USER_SEQ integer not null, reg timestamp, primary key (SHOP_SEQ, USER_SEQ) ) alter table UserShop add constraint FKxjfywgcu7nyi1lfdq0elefqx foreign key (SHOP_SEQ) references CoffeShop alter table UserShop add constraint FK7t3g73sa7nsvtdikuq8lon55 foreign key (USER_SEQ) references User ▼실행
  • 51. Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); User user1 = new User(); user1.setName("name1"); User user2 = new User(); user2.setName("name2"); CoffeShop shop1 = new CoffeShop(); shop1.setName("shop1"); CoffeShop shop2 = new CoffeShop(); shop2.setName("shop2"); session.save(user1); session.save(user2); session.save(shop1); session.save(shop2); UserShop userShop = new UserShop(); userShop.setUser_seq(user1.getSeq()); userShop.setShop_seq(shop1.getSeq()); session.save(userShop); userShop = new UserShop(); userShop.setUser_seq(user1.getSeq()); userShop.setShop_seq(shop2.getSeq()); session.save(userShop); session.flush(); session.clear(); UserShop byDB = session.get(UserShop.class,new UserShopPK(100,101)); log.debug(byDB.getUser().getName()+"<--->"+byDB.getShop().getName()); session.getTransaction().commit(); insert into User (name, USER_SEQ) values (?, ?) 16:55:03 TRACE : binding parameter [1] as [VARCHAR] - [name1] 16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100] 16:55:03 DEBUG : Reusing batch statement 16:55:03 DEBUG : insert into User (name, USER_SEQ) values (?, ?) 16:55:03 TRACE : binding parameter [1] as [VARCHAR] - [name2] 16:55:03 TRACE : binding parameter [2] as [INTEGER] - [101] 16:55:03 DEBUG : Executing batch size: 2 16:55:03 DEBUG : insert into CoffeShop (close, name, open, SHOP_SEQ)
  • 52. values (?, ?, ?, ?) 16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [null] 16:55:03 TRACE : binding parameter [2] as [VARCHAR] - [shop1] 16:55:03 TRACE : binding parameter [3] as [TIMESTAMP] - [null] 16:55:03 TRACE : binding parameter [4] as [INTEGER] - [100] 16:55:03 DEBUG : Reusing batch statement 16:55:03 DEBUG : insert into CoffeShop (close, name, open, SHOP_SEQ) values (?, ?, ?, ?) 16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [null] 16:55:03 TRACE : binding parameter [2] as [VARCHAR] - [shop2] 16:55:03 TRACE : binding parameter [3] as [TIMESTAMP] - [null] 16:55:03 TRACE : binding parameter [4] as [INTEGER] - [101] 16:55:03 DEBUG : Executing batch size: 2 16:55:03 DEBUG : insert into UserShop (reg, SHOP_SEQ, USER_SEQ) values (?, ?, ?) 16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [2016-12-18 16:55:03.706] 16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100] 16:55:03 TRACE : binding parameter [3] as [INTEGER] - [100] 16:55:03 DEBUG : Reusing batch statement 16:55:03 DEBUG : insert into UserShop (reg, SHOP_SEQ, USER_SEQ) values (?, ?, ?) 16:55:03 TRACE : binding parameter [1] as [TIMESTAMP] - [2016-12-18 16:55:03.73] 16:55:03 TRACE : binding parameter [2] as [INTEGER] - [101] 16:55:03 TRACE : binding parameter [3] as [INTEGER] - [100] 16:55:03 DEBUG : Executing batch size: 2 16:55:03 DEBUG : select usershop0_.SHOP_SEQ as SHOP_SEQ1_2_0_, usershop0_.USER_SEQ as USER_SEQ2_2_0_, usershop0_.reg as reg3_2_0_, coffeshop1_.SHOP_SEQ as SHOP_SEQ1_0_1_, coffeshop1_.close as close2_0_1_, coffeshop1_.name as name3_0_1_, coffeshop1_.open as open4_0_1_, user2_.USER_SEQ as USER_SEQ1_1_2_, user2_.name as name2_1_2_ from UserShop usershop0_ left outer join CoffeShop coffeshop1_ on usershop0_.SHOP_SEQ=coffeshop1_.SHOP_SEQ left outer join User user2_
  • 53. on usershop0_.USER_SEQ=user2_.USER_SEQ where usershop0_.SHOP_SEQ=? and usershop0_.USER_SEQ=? 16:55:03 TRACE : binding parameter [1] as [INTEGER] - [101] 16:55:03 TRACE : binding parameter [2] as [INTEGER] - [100] 집고 넘어가기 : 여기서 알수 있듯. ManyToOne쪽에서 @JoinColumn(name="SHOP_SEQ",referencedColumnName = "SHOP_SEQ", insertable = false, updatable = false) name은 자기자신 테이블의 컬럼명이다 (FK) 관계테이블은 inser와 update가 이루어지면 안되므로 false를 주었다 ▼ 실행 User userBydb = session.get(User.class,100); select user0_.USER_SEQ as USER_SEQ1_1_0_, user0_.name as name2_1_0_ from User user0_ where user0_.USER_SEQ=? 09:55:20 TRACE : binding parameter [1] as [INTEGER] - [100] select shops0_.USER_SEQ as USER_SEQ2_2_0_, shops0_.SHOP_SEQ as SHOP_SEQ1_2_0_, shops0_.SHOP_SEQ as SHOP_SEQ1_2_1_, shops0_.USER_SEQ as USER_SEQ2_2_1_, shops0_.reg as reg3_2_1_, coffeshop1_.SHOP_SEQ as SHOP_SEQ1_0_2_, coffeshop1_.close as close2_0_2_, coffeshop1_.name as name3_0_2_, coffeshop1_.open as open4_0_2_ from UserShop shops0_ left outer join COFFESHOP coffeshop1_ on shops0_.SHOP_SEQ=coffeshop1_.SHOP_SEQ where shops0_.USER_SEQ=? 09:55:20 TRACE : binding parameter [1] as [INTEGER] - [100]
  • 54. 캐싱 1 차 캐시 Session 객체와 관련하여 트랜잭션이 보장되는 캐시다. 이는 세션의 수명이 유지되는 동안이나 컨버세이션 conversation 내에서만 가능하다. 1 차 캐시는 하이버네이트 프레임워크에서 기본으로 제공한다. //팩토리 가져오기 SessionFactory factory = d.getSessionFactory(); //세션가져오기 Session session = factory.getCurrentSession(); //트랜젝션 시작 session.beginTransaction(); User user = new User("admin","admin_pwd"); Set<Auth> auths = new HashSet<Auth>(); auths.add(new Auth("fName1","fCode","fVal")); auths.add(new Auth("fName2","fCode","fVal")); auths.add(new Auth("fName3","fCode","fVal")); user.setAuths(auths); session.save(user); user = new User("user","user_pwd"); auths = new HashSet<Auth>(); auths.add(new Auth("fName1_1","fCode","fVal")); auths.add(new Auth("fName1_2","fCode","fVal")); auths.add(new Auth("fName1_3","fCode","fVal")); user.setAuths(auths); session.save(user); //1차 캐쉬부분 읽어오기 User load_user = (User) session.load(User.class,new Integer(50)); System.out.println(load_user); load_user.setId("admin_after"); session.save(user); //트랜젝션 커밋 session.getTransaction().commit(); //트랜젝션이 닫혔기때문에 아래 부분은 오류남 User after_load_user = (User) session.load(User.class,new Integer(50)); System.out.println(after_load_user); Hibernate: drop table T_AUTH if exists Hibernate: drop table T_USER if exists Hibernate: drop table T_USER_PRIVACY if exists Hibernate: drop sequence SEQ_USER Hibernate: create table T_AUTH (FNC_ID integer generated by default as identity (start with 1), fncCode varchar(255), fncName varchar(255), fncValue varchar(255), user_SEQ integer, primary key (FNC_ID)) Hibernate: create table T_USER (SEQ integer not null, ID varchar(255), PWD varchar(255), primary key (SEQ)) Hibernate: create table T_USER_PRIVACY (SEQ integer generated by default as identity (start with 1), AGE integer, NAME varchar(255), primary key (SEQ)) Hibernate: alter table T_AUTH add constraint FK_5wrx276k6vlwxjehoqh6h1rwa foreign key (user_SEQ) references T_USER Hibernate: create sequence SEQ_USER start with 1 Hibernate: call next value for SEQ_USER Hibernate: insert into T_USER (ID, PWD, SEQ) values (?, ?, ?) Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?)
  • 55. Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?) Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?) Hibernate: insert into T_USER (ID, PWD, SEQ) values (?, ?, ?) Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?) Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?) Hibernate: insert into T_AUTH (FNC_ID, fncCode, fncName, fncValue, user_SEQ) values (default, ?, ?, ?, ?) User(seq=50, id=admin, password=admin_pwd, auths=[Auth(fncId=1, fncName=fName2, fncCode=fCode, fncValue=fVal, user=null), Auth(fncId=2, fncName=fName3, fncCode=fCode, fncValue=fVal, user=null), Auth(fncId=3, fncName=fName1, fncCode=fCode, fncValue=fVal, user=null)]) Hibernate: update T_USER set ID=?, PWD=? where SEQ=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Hibernate: update T_AUTH set USER_SEQ=? where FNC_ID=? Exception in thread "main" org.hibernate.SessionException: Session is closed! 두 번째로 User 객체를 불렀을 때(load) 세션 자체가 가진 캐시에서 해당 객체를 조회한다. 이렇게 데이터베이스를 이용하는 네트워 크 라운드트립roundtrip을 피한다. 세션 캐시는 클래스 타입과 함께 명시되므로 이미 존재하는 인스턴스를 오버라이드할 때 좀 더 주의해야 한다. 2 차 캐시 SessionFactory 클래스를 이용하여 전역에서 사용할 수 있다. 그래서 2 차 캐시 안에 있는 어떤 데이터라도 애플리케이션 전체에서 사용이 가능하다. 하이버네이트는 EhCache 와 InfiniSpan 같은 오픈소스 캐시 라이브러리를 지원한다. 사용자 정의 캐시 라이브러리를 사용하려면 org.hibernate.cache.spi.CacheProvider 인터페이스 관련 라이브러리를 구현하면 된다. 하이버네이트는 기본 옵션으로 EhCache 를 2 차 캐시 공급자로 사용한다. 캐시 공급자를 연결하려면 hibernate.cache.provider 클래스 속성에 캐시 공급자를 명시한다. 다음은 JBoss 의 InifiniSpan 을 캐시 공급자로 연결하는 예다. <hibernate-configuration> <session-factory> .... <!-- Infinispan 캐시 공급자 지정 --> <property name="hibernate.cache.provider_class"> org.hibernate.cache.infinispan.InfinispanRegionFactory </property> </session-factory> </hibernate-configuration> 다양한 캐시 속성을 이용하여 캐싱할 클래스별로 캐싱 정책을 지정할 수 있다. 매 핑 정의 파일의 class 태그 안에 cache 속성을 확인해 보자. cache usage 속성 transactional 이 전략은 트랜잭션이 가능한 캐시를 지원하는 캐시 공급자를 위해 제공된다. 모 든 캐시 공급자가 트랜잭션이 가능한 캐싱 상품을 가지고 있지 않다는 점에 유의 하자.
  • 56. read-only 갱신할 필요가 없는 영속화 객체에 자주 접근한다면 read-only 를 선택한다. 데 이터베이스를 거의 변경하지 않거나 전혀 변경하지 않는다면 이 옵션으로 성능 이 크게 향상할 것이다. read-write 객체를 데이터베이스에서 읽거나 데이터베이스에 쓸 때 이 방법을 사용한다. nonstrict-read-write 객체를 그다지 자주 갱신하지 않을 때 사용한다. 이 옵션을 전역에서 사용하려면 설정 파일에서 hibernate.cache.default_cache_concurrency_strategy 속성을 설정한다. 쿼리 캐시 객체 뿐만 아니라 쿼리도 캐싱할 수도 있다. 특정 쿼리를 빈번하게 사용한다면 캐싱하는 것을 추천한다. 이 기능을 사용하려면 hibernate.cache.use_query_cache 속성을 true 로 지정한다. 코드에 한 가지를 더 추가해야 하는데, Query.setCacheable() 메소드를 호출해서 Query 의 cacheable 속성을 true 로 지정해야한다.
  • 57. 상속 전략 Entity 상속으로 처리하는 방법이 3가지 있다. 1. Table-per-Class 전략 2. Table-per-Subclass 전략 3. Table-per-Concrete-Class 전략 @Table-per-Class 기본값 @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @Data @Entity //@Inheritance(strategy=InheritanceType.SINGLE_TABLE) @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 50, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME) Integer seq; String name; } @Data @Entity public class UserBankPrivacy extends User{ String password; } @Data @Entity public class UserBodyPrivacy extends User{ Integer age; double weight; double height; } @Data @Entity public class UserOfficePrivacy extends User{ String address; } create table USER ( seq integer not null, name varchar(255), age integer, height double, weight double, password varchar(255), address varchar(255), primary key (seq) ) ▼실행 insert into User (name, address, DTYPE, seq)
  • 58. values (?, ?, 'UserOfficePrivacy', ?) 16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name1] 16:31:06 TRACE : binding parameter [2] as [VARCHAR] - [addr1] 16:31:06 TRACE : binding parameter [3] as [INTEGER] - [50] insert into User (name, age, height, weight, DTYPE, seq) values (?, ?, ?, ?, 'UserBodyPrivacy', ?) 16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name2] 16:31:06 TRACE : binding parameter [2] as [INTEGER] - [2] 16:31:06 TRACE : binding parameter [3] as [DOUBLE] - [2.0] 16:31:06 TRACE : binding parameter [4] as [DOUBLE] - [2.0] 16:31:06 TRACE : binding parameter [5] as [INTEGER] - [51] 16:31:06 DEBUG : insert into User (name, password, DTYPE, seq) values (?, ?, 'UserBankPrivacy', ?) 16:31:06 TRACE : binding parameter [1] as [VARCHAR] - [name2] 16:31:06 TRACE : binding parameter [2] as [VARCHAR] - [pwd] 16:31:06 TRACE : binding parameter [3] as [INTEGER] - [52] 집고 넘어가기 : DTYPE 저것은 무엇일까 왜 들어가는걸까? 하이버네이트에서 자동으로 어떤 클래스에의해 INSERT가 되었는지 출처를 알수 있도록 DTYPE이라는 컬럼을 만들어 넣어버렸다. 문제를 해결해보자 ▼DTYPE컬럼명을 변경해보자 @DiscriminatorColumn @DiscriminatorColumn(name="CLASS_TYPE", discriminatorType=DiscriminatorType.STRING) @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 50, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME) Integer seq; String name; } ▼DTYPE컬럼을 값을 각각 클래스 별로 다르게 지정해보자 @DiscriminatorValue @Data @Entity @DiscriminatorValue(value="BANK") public class UserBankPrivacy extends User{ String password; @Data @Entity @DiscriminatorValue(value="BODY") public class UserBodyPrivacy extends User{ Integer age; @Data @Entity @DiscriminatorValue(value="OFFICE") public class UserOfficePrivacy extends User{ String address;
  • 59. } double weight; double height; } } ▼DTYPE컬럼을 없에보자 (사용하지 않기) @DiscriminatorFormula @Data @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="CLASS_TYPE", discriminatorType=DiscriminatorType.STRING) @DiscriminatorFormula("...") @DiscriminatorValue(value="USER") @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, initialValue = 50, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME) Integer seq; String name; } create table User ( seq integer not null, name varchar(255), age integer, height double, weight double, password varchar(255), address varchar(255), primary key (seq) ) ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserOfficePrivacy userOffice = new UserOfficePrivacy(); userOffice.setName("name1"); userOffice.setAddress("addr1"); session.save(userOffice); UserBodyPrivacy userBody = new UserBodyPrivacy(); userBody.setName("name2"); userBody.setAge(2); userBody.setWeight(2.0); userBody.setHeight(2.0); session.save(userBody); UserBankPrivacy userBank = new UserBankPrivacy(); userBank.setName("name2"); userBank.setPassword("pwd"); session.save(userBank); session.getTransaction().commit(); insert into User (name, address, seq) values (?, ?, ?) 16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name1] 16:40:56 TRACE : binding parameter [2] as [VARCHAR] - [addr1]
  • 60. 16:40:56 TRACE : binding parameter [3] as [INTEGER] - [50] insert into User (name, age, height, weight, seq) values (?, ?, ?, ?, ?) 16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name2] 16:40:56 TRACE : binding parameter [2] as [INTEGER] - [2] 16:40:56 TRACE : binding parameter [3] as [DOUBLE] - [2.0] 16:40:56 TRACE : binding parameter [4] as [DOUBLE] - [2.0] 16:40:56 TRACE : binding parameter [5] as [INTEGER] - [51] insert into User (name, password, seq) values (?, ?, ?) 16:40:56 TRACE : binding parameter [1] as [VARCHAR] - [name2] 16:40:56 TRACE : binding parameter [2] as [VARCHAR] - [pwd] 16:40:56 TRACE : binding parameter [3] as [INTEGER] - [52] 집고 넘어가기 : @Inheritance 어노테이션을 엔티티에 명시함으로써 Table-per-class 상속 전략을 정의한다. 이 어노테이션의 경우 strategy 변수를 통해 전략을 지정하는데, 여기서는 SINGLE_TABLE 전략을 사용한다. 그 외에도 InheritanceType 에는 TABLE_PER_CLASS 와 JOINED 전략이 있다. Table-per-class 전략을 이용할 때 InheritanceType.TABLE_PER_CLASS 값으로 잘못 지정할 수도 있다. 반드시 SINGLE_TABLE 만 사용하자. Table-perclass 전략에서는 하위 클래스와 관련된 컬럼에는 NOT NULL 제약사항을 선언하지못한다는 점에 주의하자.
  • 61. @Table-per-Subclass Table-per-class 전략에서 구별자 컬럼을 이용하여 구분할 수있었지만 하나의 테이블을 이용하는 대신에 분리된 테이블을 사용하는 방법도 제공한다. @Table-per-subclass @Inheritance(strategy=InheritanceType.JOINED) @Data @Entity @Inheritance(strategy=InheritanceType.JOINED) @DiscriminatorValue(value="USER") @SequenceGenerator(name = UserSubClass.SEQ_NAME, sequenceName = UserSubClass.SEQ_NAME, initialValue = 50, allocationSize = 1) public class UserSubClass { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME) Integer seq; String name; } Data @Entity public class UserBankPrivacy extends UserSubClass{ String password; } @Data @Entity public class UserBodyPrivacy extends UserSubClass{ Integer age; double weight; double height; } @Data @Entity public class UserOfficePrivacy extends UserSubClass{ String address; } ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); UserSubClass user = new UserSubClass(); user.setName("nameOffice"); session.save(user); UserOfficePrivacy userOffice = new UserOfficePrivacy(); userOffice.setName("nameOffice"); userOffice.setAddress("addr"); session.save(userOffice); UserBodyPrivacy userBody = new UserBodyPrivacy(); userBody.setName("nameBody"); userBody.setAge(2); userBody.setWeight(2.0); userBody.setHeight(2.0); session.save(userBody); UserBankPrivacy userBank = new UserBankPrivacy(); // userBank.setName("nameBank"); userBank.setPassword("pwd"); session.save(userBank); session.getTransaction().commit();
  • 62. 집고 넘어가기 : 부모 Entity 의 TABLE 을 기준으로 자식 테이블 내용이 들어간다 userBank Entity 를 넣을때 보면 부모 Entity 의 name 값을 넣지 않아도 부모 시퀀스가 들어가고 난뒤 자식 Entity 의 테이블에 내용이 들어간다 부모 SEQ ID 컬럼과 자식들의 SEQ 컬럼은 자동 생성된다.(부모 ID Column 이름으로) @Table-per-Concrete-Class 부모 클래스의 모든 속성이 자식 클래스와 관련된 테이블에 복사된다. (자주 쓰이진 않는다) @Data @Entity @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) @DiscriminatorValue(value="USER") @SequenceGenerator(name = UserConcreteClass.SEQ_NAME, sequenceName = UserConcreteClass.SEQ_NAME, initialValue = 50, allocationSize = 1) public class UserConcreteClass { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_NAME) @Column(name = "USER_SEQ") Integer seq; String name; } @Data @Entity public class UserBankPrivacy extends UserConcreteClass { String password; } @Data @Entity public class UserBodyPrivacy extends UserConcreteClass { Integer age; double weight; double height; } @Data @Entity public class UserOfficePrivacy extends UserConcreteClass { String address; }
  • 63. 하이버네이트 질의어 HQL(Hibernate Query Language)에서는 WHERE, ORDER BY, AVG, MAX 등을 SQL 처럼 사용할수 있습니다. HQL 은 객체(Entity)를 사용합니다. 테이블을 나타내는 자리에 엔티티 객체 클래스명을 사용해야 합니다. Query Class 사용하기 @Data @Entity @SequenceGenerator(name = User.SEQ_NAME, sequenceName = User.SEQ_NAME, allocationSize = 1) public class User { public static final String SEQ_NAME = "SEQ_USER"; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @Column(name = "USER_SEQ") Integer seq; String name; String password; } @Data @Entity public class Office { @Id @Column(name = "OFFICE_SEQ") Integer seq; String addr; } ▼실행 Session session = getSessionFactory().getCurrentSession(); session.beginTransaction(); User user = new User(); user.setName("nanme1"); user.setPassword("pwd1"); session.save(user); Office office = new Office(); office.setSeq(user.getSeq()); office.setAddr("guro1"); session.save(office); user = new User(); user.setName("nanme2"); user.setPassword("pwd2"); session.save(user); office = new Office(); office.setSeq(user.getSeq());
  • 64. office.setAddr("guro2"); session.save(office); session.flush(); session.clear(); Query query = session.createQuery("from User"); List list = query.getResultList(); list.forEach(it->log.debug("-->"+it.toString())); session.getTransaction().commit(); select user0_.USER_SEQ as USER_SEQ1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ 11:05:18 DEBUG : -->User(seq=1, name=nanme1, password=pwd1) 11:05:18 DEBUG : -->User(seq=2, name=nanme2, password=pwd2) ▼실행 (where) Query query = session.createQuery("from User as A where A.seq = 1"); select user0_.USER_SEQ as USER_SEQ1_1_, user0_.name as name2_1_, user0_.password as password3_1_ from User user0_ where user0_.USER_SEQ=1 -->User(seq=1, name=nanme1, password=pwd1) ▼실행 (join) (모든객체 받아오기) Object[] 로받아온다. Query query = session.createQuery("from User as A, Office as B where A.seq = 1 AND A.seq = B.seq"); List<Object[]> list = query.getResultList(); list.forEach(it->{ for (Object entity : it) { log.debug("-->"+entity.toString()); } }); select user0_.USER_SEQ as USER_SEQ1_1_0_, office1_.OFFICE_SEQ as OFFICE_S1_0_1_, user0_.name as name2_1_0_, user0_.password as password3_1_0_, office1_.addr as addr2_0_1_ from User user0_ cross join Office office1_ where user0_.USER_SEQ=1 and user0_.USER_SEQ=office1_.OFFICE_SEQ 11:10:44 DEBUG : -->User(seq=1, name=nanme1, password=pwd1) 11:10:44 DEBUG : -->Office(seq=1, addr=guro1)
  • 65. ▼실행 (join) (특정객체 받아오기) @Data @Entity public class Office { @Id @Column(name = "OFFICE_SEQ") Integer seq; String addr; public Office() { } public Office(Integer seq, String addr){ this.seq = seq; this.addr = addr; } } 필드를받을수 있는 생성자를 생성한다 Query query = session.createQuery("SELECT new Office (B.seq,B.addr) from User as A, Office as B where A.seq = 1 AND A.seq = B.seq"); List<Office> list = query.getResultList(); list.forEach(it->log.debug("-->"+it.toString())); select office1_.OFFICE_SEQ as col_0_0_, office1_.addr as col_1_0_ from User user0_ cross join Office office1_ where user0_.USER_SEQ=1 and user0_.USER_SEQ=office1_.OFFICE_SEQ 11:15:27 DEBUG : -->Office(seq=1, addr=guro1) ▼실행 (그룹함수) Query query = session.createQuery("SELECT MAX(A.seq) from User as A"); Object result = query.getSingleResult(); log.debug("-->"+result); select max(user0_.USER_SEQ) as col_0_0_ from User user0_ -->2 ▼실행 (그룹함수 복합) Query query = session.createQuery("SELECT MAX(A.seq), MIN(A.seq), AVG(A.seq), COUNT(*) from User as A"); Object[] results = (Object[]) query.getSingleResult(); for (Object entity : results) { log.debug("-->"+entity.toString()); } select max(user0_.USER_SEQ) as col_0_0_, min(user0_.USER_SEQ) as col_1_0_, avg(cast(user0_.USER_SEQ as double)) as col_2_0_, count(*) as col_3_0_ from User user0_ 11:27:35 DEBUG : -->2 11:27:35 DEBUG : -->1 11:27:35 DEBUG : -->1.5 11:27:35 DEBUG : -->2 ▼HSQL에 변수 바인딩하기 ? 처리 Query query = session.createQuery("from User as A where A.seq = ?"); query.setParameter(0, new Integer(1)); ▼HSQL에 변수 바인딩하기 치환이름 처리 Query query = session.createQuery("from User as A where A.seq = :seq"); query.setParameter("seq", new Integer(1)); ▼HSQL에 변수 바인딩하기 in조건 list 처리