목차

Spring에 JPA 적용하기

  • description : JPA 관련 내용 기술
  • author : 도봉산핵주먹
  • email : hylee@repia.com
  • lastupdate : 2020-12-16

JPA 사용해야 되는 이유

JPA 어려운 이유

JPA 설계

JPA 요점

매핑

@Entity : JPA가 관리함을 의미
  public, protected 생성자, 기본 생성자 필수
  @Table(name = "데이터베이스 테이블명")

실행 시점에 테이블 자동 생성 가능
  create, create-drop, update, validate, auto, none
  개발 초기 단계는 create, update
  테스트 서버는 update, validate
  스테이징, 운영 서버 validate, none
  
  @Column의 옵션은 JPA 로직과는 상관이 없고, 생성시 ddl에 영향을 줌
  updatable = false, 업데이트시에 업데이트 하지 않음
  nullable = false 
  unique = true, 생성시 이름이 

  sql은 date=날짜, time=시간, tiemstamp만 있음

  GeneratorType.IDENTIFY
  persist()하면 쿼리가 실행됨
  
  GeneratorType.SEQUENCE
  트랜잭션을 해야해야 쿼리가 실행됨

  실전 예제
  

연관관계 매핑

'객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.' 
- 조영호(객체지향의 사실과 오해), 오브젝트

JPA 포인터 역할
양방향 연관관계의 주인

객체와 테이블의 차이를 이해해야 됨
테이블은 외래키만으로 양방향이 모두 적용됨(조인...)
객체는 그렇지 않음

양방향, 단방향... 양방향이 좋은가? 기본적으로 단방향으로 하는게 좋다.
설계시에 단방향 매핑만으로 이미 연관관계 매핑 완료하자
개발시에 꼭 필요하면 추가 하자. 

양방향 연관관계 주의

다양한 연관단계 매핑

매핑 추가 정보

관계형 데이터베이스는 상속관계가 없다

객체는 상속관계는 데이터 베이스에서는 조인, 단일 테이블, 각각테이블로 구현이 가능함
기본 전략은 단일 테이블

@MappedSuperclass - 자주 애용하자
테이블과 관계가 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할
abstract(추상 클래스)로 사용

주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용

@Entity 클래서는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능 

프록시와 연관관계 매핑

멤버를 조회할때 팀도 함께 조회를 해야할까?
em.getReference() // 

프록시 클래스
  실제 클래스를 상속 받아서 만들어짐
  실제 클래스와 겉 모양이 같다.
  
특징
  프록시 객체는 처음 사용할 때 한 번만 초기화(DB에서 가져와 
  프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니고, 초기화 이후 프록시 객체를 통해서 실제 엔티티에 접근 가능

  프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 == 대신 instanceof 사용해야 됨

  영속성 컨텍스트에 엔티티가 있으면 em.getReference()를 호출해도 실제 엔티티 반환(프록시 아님)
  
  영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
  
  JPA내에서는 프록시든, 객체든 항상 == true 임을 보장함
  
  프록시 인스턴스의 초기화 여부 확인
  PersistenceUnitUtil.isLoaded(Object entity)
  
  프록시 클래스 확인 방법
  entity.getClass().getName() 출력
  
  프록시 강제 초기화, JPA는 강제 초기화가 없음
  org.hibernate.Hibernate.initialize(entity);
  
  즉시 로딩(EAGER), 지연 로딩(LAZY)
  
  실무에서 가급적 모든 연관관계는 지연 로딩만 사용
  
  즉시 로딩은 JPQL에서 N+1 문제 발생
  
  @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
  
  @OneToMany, @ManyToMany는 기본이 지연 로딩
  
  @필요하면 Fetch join, annotation, 배치 사이즈로 사용함
  
  모든 연관관계에 지연 로딩을 사용하라
  실무에서 즉시 로딩을 사용하지 마라!, JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!
  즉시 로딩은 상상하지 못한 쿼리가 나간다.

영속성 전이(CASCADE)
  영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
  다만 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
  
  CASCADE.ALL or CASCADE.PERSIST를 주로 사용
  게시판 - 첨부 파일의 경우 처럼 거의 라이프사이클이 같을 때 유용하게 사용
  소유자가 하나일때 사용
  
고아 객체(삭제는 언제나 조심해서 사용)
  orphanRemoval = true
  연결고리가 없어졌을때 삭제 됨
  참조하는 곳이 하나일 때 사용해야 됨
  특정 엔티티가 개인 소유할 때 사용 (@OneToOne, @OneToMany만 가능)
  
CASCADE.ALL, orphanRemoval = true를 모두 사용하면
부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음(DDD의 Aggregate Root개념을 구현할 때 유용)

실전: 

값 타입

자바 기본값 타입은 공유가 불가능(복사)
래퍼 클래스는 공유가 가능하지만 변경할 방법이 없음 

임베디드 타입(클래스 생성 -> 복합값을 가짐)
  재사용 가능
  높은 응집도를 가짐
  엔티티가 생명 주기를 조절
  객체와 테이블을 아주 세밀하게 매핑하는 것이 가능( find-grained)
  잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.
  
동일한 타입을 중복으로 사용하면 중복필드로 오류가 남
  @AttributeOverrides, @AttributeOverride 사용
  

임베디드 타입은 복사해서 사용해야 됨 (= <-레퍼런스(참조) 복사, new 사용해야 됨)
공유로 인한 사이드 이펙트는 주의해야 한다.
대신 불변 객체(immutable object)로 설계하거나 생성자로만 값을 설정하고, 수정자로는 변경이 안되게 함(불변객체, 없애거나 private로 함)
변경하려면 new를 기존 값을 가져오고, 변경하고 싶은 것만 변경하면 됨(임베디드 타입 객체 전체를 바꿔야 함. 일부만 바꾸지 말자!!)

실전: 임베디드 타입은 사용할 경우 불변 객체로 해서 사용하면 주석을 꼭 만들자!!

기본 값타입이 인스턴스가 달라도 값이 같으면 같다.

값 비교시
동일성(identity) 비교: 인스턴스의 참조 값 비교, ==
동등성(equivalence) 비교: 인스턴스의 값 비교, equals()

객체 비교시 equals() override하여 전체 값을 일일이 동등성 비교를 해야 됨. (툴에서 자동으로 제공하는 것으로 사용, hashCode도 함께...)

값타입 컬렉션(@ElementCollection, @CollectionTable 사용)
  엔티티로 컬렉션이 아닌 값타입으로 컬렉션을 사용
  데이터베이스로 봐서는 테이블이 분리된 것임
  테이블이 분리되었어도 생명주기가 엔티티에 의해 관리가 됨 즉 영속성 전이, 고아 객체 제거 기능을 필수로 가지는 것과 같음
  값 타입 컬렉션도 지연 로딩 전략을 사용함
  
  수정시에는 세터가 아닌 new를 사용하여 통으로 교체(side effect를 원천적으로 없앰)
  
  값 타입은 엔티티와 다르게 식별가 개념이 없다.
  값은 변경하면 추적이 어렵다.
  값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고,
  값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
  
  값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 함(null X, 중복 x)
  실무에서는 값타입을 엔티티로 승격하여 사용(일대다 관계)
  값 타입 컬렉션은 단순한 값 저장시에만 사용

객체 지향 쿼리 언어

적용파일 목록

context-hibernate.xml

Tip

아래 코드에서 3곳만 고치면 된다.

<property name=“dataSource” ref=“” />

<property name=“packagesToScan” value=“” >

<prop key=“hibernate.dialect”>



<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
 
	<!-- 컨테이너가 관리하는 EntityManager 생성, @PersistenceContext와 함께 사용 -->
	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="egov.dataSource" />
		<!-- 어노테이션 매핑정보 스캔 -->
		<property name="packagesToScan" value="com" />
		<!-- 구현체별 자체 기능을 표준화 -->
		<property name="jpaVendorAdapter">
			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
				<property name="showSql" value="true" />
				<property name="generateDdl" value="true" />
			</bean>
		</property>
		<!-- persistence.xml 설정정보와 함께 사용가능 -->
		<property name="jpaProperties">
			<props>
				<!-- <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.EJB3NamingStrategy</prop> -->
				<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
				<!-- hibernate 5 버전에서의 세팅 (4 버전은 위에껄 사용)-
				<prop key="hibernate.naming.implicit-strategy">org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl</prop>
				<prop key="hibernate.naming.physical-strategy">org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl</prop> 
				-->
				<prop key="hibernate.hbm2ddl.auto">validate</prop>
				<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
				<prop key="hibernate.show_sql">true</prop>                
				<prop key="hibernate.format_sql">true</prop>                
				<prop key="hibernate.use_sql_comments">true</prop>         
				<prop key="hibernate.jdbc.batch_size">5</prop>
			</props>
		</property>
	</bean>
 
</beans>



hibernate.dialect 정보

DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL5 org.hibernate.dialect.MySQL5Dialect
MySQL5 with InnoDB org.hibernate.dialect.MySQL5InnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version) org.hibernate.dialect.OracleDialect
Oracle 9i org.hibernate.dialect.Oracle9iDialect
Oracle 10g org.hibernate.dialect.Oracle10gDialect
Oracle 11g org.hibernate.dialect.Oracle10gDialect
Sybase org.hibernate.dialect.SybaseASE15Dialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server 2000 org.hibernate.dialect.SQLServerDialect
Microsoft SQL Server 2005 org.hibernate.dialect.SQLServer2005Dialect
Microsoft SQL Server 2008 org.hibernate.dialect.SQLServer2008Dialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
H2 Database org.hibernate.dialect.H2Dialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect

context-jpa-respository.xml

Tip

아래 코드에서 한개만 고치면 된다.

<jpa:repositories base-package=“”>



<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
 
	<jpa:repositories base-package="com"></jpa:repositories>
 
</beans>

POM.xml

	<properties>
		<egovframework.jpa.version>3.7.0</egovframework.jpa.version>
		<hibernate.version>4.3.11.Final</hibernate.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>egovframework.rte</groupId>
			<artifactId>egovframework.rte.psl.data.jpa</artifactId>
			<version>${egovframework.jpa.version}</version>
		</dependency>
 
		<!-- Hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
 
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.4.2.Final</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jpa</artifactId>
			<version>1.11.10.RELEASE</version>
			<exclusions>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>slf4j-api</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.slf4j</groupId>
					<artifactId>jcl-over-slf4j</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

Builder 사용하기

Troubleshooting

Ref

Querydsl