[JPA-10] 객체지향 쿼리 언어 -2

이가희·2024년 12월 29일
0

JPA

목록 보기
11/16
post-thumbnail

이번에는 QueryDSL부터 살펴보겠다.
QueryDSL은 데이터를 조회하는데 특화된 오픈소스 프로젝트이다.


1. QueryDSL 설정

여기로[클릭] 가서
querydsl-jpa (QueryDSL JPA 라이브러리) 와
querydsl-apt (쿼리 타입 (Q)를 생성할 때 필요한 라이브러리)
의존성을 받아 라이브러리를 다음과 같이 추가한다.

<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa -->
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.0.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-apt -->
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.0.0</version>
</dependency>

그리고 다음과 같이 쿼리 타입 생성용 플러그인을 pom.xml에 추가한다.

			<plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

이렇게 하면 컴파일 시 outputDirectory에 지정한 위치에 QMember.java처럼 Q로 시작하는 쿼리 타입들이 생성된다.

2. QueryDSL 사용

EntityManager em = emf.createEntityManager();

JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m")l // 별칭이 m
List<Member> members = 
		query.from(qMember)
        	.where(qMember.name.eq("회원1")
            .orderyBy(qMember.name.desc())
            .list(qMember);
           

QueryDSL를 사용하려면 먼저, JPAQuery 객체를 생성해야 해야한다.
이때 생성자의 파라미터로 엔티티 매니저를 넘겨주면 된다.
그리고 사용할 쿼리 타입 (Q)를 생성하는데 생성자에게는 별칭을 주면 된다.
이때 쿼리 타입에 따로 별칭을 주지 않아도 될 경우
import static jpabook.jpashop.domain.QMember.member
을 통해 기본 인스턴스를 사용할 수도 있다.

사용방법은 정말 쉽고 직관적이다.
위의 쿼리를 실행하면 회원 테이블에 이름이 회원1인 회원들이 이름의 역순으로 정렬되어 조회될 것이다.

✏️ 검색 조건 쿼리

기본 쿼리 기능을 알아보겠다.
QueryDSL의 where절에는 and 나 or을 사용할 수 있다.
여러 검색 조건도 사용할 수 있는데, 이때는 and 연산이 된다.
(ex wehre (item.name.eq("상품"), item.price.gt(2000))

startsWith, contains, between을 등 필요한 대부분의 메소드를 제공하고 있으니, IDE가 제공하는 코드 자동 완성 기능의 도움을 받으면 필요한 메소드를 어렵지 않게 찾ㅇ르 수 있을 것이다.

✏️ 결과 조회

대표적인 결과 조회 메소드만 알아보겠다.

  1. uniqueResult() : 조회 결과가 한 건일 때 사용. 결과가 없으면 null을 반환하고, 두 건 이상이면 NonUniqueResultException 예외가 발생함.
  2. singleResult() : uniqueResult()와 유사하나, 결과가 하나 이상이면 처음 데이터를 반환함.
  3. list() : 결과가 다 건일 때 사용. 결과가 없으면 빈 컬렉션을 반환함.

✏️ 페이징과 정렬


QItem item = QItem.item;

SearchResults<Item> result =
	query.from(item)
	.where(item.price.gt(20000))
    .orderBy(item.price.desc())
    .offset(10).limit(20)
    .listResults(item);
    
long total = result.getTotal(); //검색된 전체 데이터 수
List<Item> results = result.getResults();//조회된 데이터

   

간단하게 페이징 처리를 할 수 있다.
이때 결과 조회 메소드로 listResults()를 사용하면, 전체 데이털 조회를 위한 count 쿼리를 한 번 더 실행해서 전체 데이터 수를 조회할 수가 있다.

✏️ 그룹

sql과 크게 다르지 않다. groupBy를 사용하고, 그룹화된 결과에 조건을 걸고 싶다면 having을 사용하면 된다.

query.from(item)
	.groupBy(item.price)
    .having(item.price.gt(1000))
    .list(item);

✏️ 조인

innerJoin, leftJoin, rightJoin, fullJoin 뿐만 아니라 fetchJoin 도 사용 가능하다.

조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정, 두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정하면 된다.

//조인 몇 가지 예시
QOrder order = QOrder.order;
QMember member = QMember.member;
QOrderItem orderItem = QOrderItem.orderItem;

// 조인 on 사용
query.from(order)
	.leftJoin(order.orderItems, orderItem)
    .on(orderItem.count.gt(2))
    .list(order);

// 페치 조인 사용
query.from(order)
	.innerJoin(order.member, memver).fetch()
    .leftJoin(order.orderItems, orderItem).fetch()
    .list(order);

//from 절에 여러 조건 사용
query.from(order, member)
	.where(order.member.eq(member))
    .list(order);

✏️ 서브 쿼리

JPASubQuery를 생성해서 사용하는데, 서브 쿼리의 결과가 하나이면 unique() 다건이면 list()를 사용한다.

QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");

//단 건 예시
query.from(item)
	.where(item.price.eq(
    	new JPASubQuery().from(itemSub).unique(itemSub.price.max())
        ))
        .list(item);

//다 건 예시
query.from(item)
	.where(item.in(
     	new JPASubQuery().from(itemSub)
        .where(item.name.eq(itemSub.name))
        .list(itemSub)
        ))
     .list(item);

✏️ 프로젝션과 결과 반환

프로젝션 : select 절에 조회 대상을 지정하는 것

case 1 : 프로젝션 대상이 하나 일 때

List result = query.from(item).list(item.name);
위 처럼 해당 타입으로 반환된다.

case 2 : 여러 개 일 때

여러 필드를 선택하면 QueryDSL은 기본적으로 Tuple 이라는 Map과 유사한 타입을 사용한다. tuple.get() method에 조회한 쿼리 타입을 지정하면 조회 결과를 얻을 수 있다

List<Tuple> result = query.from(item).list(item.name, item.price);

for( Tuple tuple : result ) {
	System.out.println("name = " tuple.get(item.name));
  	System.out.println("price = " tuple.get(item.price));
}

case 3 : 특정 객체로 받고 싶을 때 (빈 생성)

쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶으면 빈 생성 기능을 사용하면 된다.

객체를 생성하는 방식은 프로퍼티 접근, 필드 직접 접근, 생성자 사용이 있다.

//예제 ItemDTO
 public class ItemDTO {
 	private String username;
 	private int price;
 
 	public ItemDTO(){}
 
 	public ItemDTO(String username, int price) {
 		this.username = username;
 		this.price = price;
 	}
 
 	//getter , setter...
}
  1. 프로퍼티 접근 방식
  	QItem item = QItem.item;
	query.from(item).list(
  		Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
  

코드가 별로 어렵지는 않다. 수정자(setter)를 이용해서 값을 채우게 되고,
이때 쿼리 결과와 매핑할 프로퍼티 이름이 다르면 as를 사용해서 별칭을 주면 된다.
여기서 ItemDTO는 username이고, 쿼리 결과는 name을 반환한다. 따라서 별칭을 사용해서 매핑시켜 주었다.

  1. 필드 직접 접근
  	QItem item = QItem.item;
	query.from(item).list(
  		Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
  

Projections.fields() method를 사용하면 필드에 직접 접근하여 값을 채운다. 필드를 private으로 설정해도 동작한다.

  1. 생성자 사용
  	QItem item = QItem.item;
	query.from(item).list(
  		Projections.constructor(ItemDTO.class, item.name, item.price));
  

이 방식을 사용하려면 당연히 지정한 프로젝션과 파라미터 순서가 같은 생성자가 필요하다.

✏️ 수정, 삭제 배치 쿼리

배치 쿼리를 지원하는데, 영속성 컨텍스트를 무시하고 데이터베이스를 직접 조회하게 됨을 유의하여 사용해야 한다.

QItem item = QItem.item;

//수정 배치 쿼리
JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where (item.name.eq("아이템"))
							.set(item.price , item.price.add(100))
							.execute();

//삭제 배치 쿼리
JPADeleteClause deleteClause = new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("아이템"))
							.execute();

사용 방법이 어렵지 않으니 넘어가겠다.

✏️ 동적 쿼리

BooleanBuilder 를 사용해서 특정 조건에 따른 동적 쿼리를 편리하게 생성할 수 있다.

SearchParam param = new SearchParam();
param.setName("개발자");
param.setPrice(1000);

QItem item = QItem.item;

BooleanBuilder builder = new BooleanBuilder();

if(StringUtils.hasText(param.getName())) {
	builder.and(item.name.contains(param.getName()));
}

if(param.getPrice() != null){
	builder.and(item.price.gt(param.getPrice()));
}

List<Item> result = query.from(item)		
							.where(builder).list(item);

✏️ 메소드 위임

메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수도 있다.

기능을 사용하려면 우선 정적 메소드를 만들고, QueryDeletegate 어노테이션에 속성으로 이 기능을 적용할 엔티티를 지정해야 한다.

public class ItemExpression {

	@QueryDelegate(Item.class)
	public static BooleanExpression isExpensive(QItem item, Inter price) {
	return item.price.gt(price);
	}
}

이렇게 하면 생성된 쿼리 타입에 해당 기능이 추가된 것을 확인 할 수 있다.
이제 위임 기능을 아래와 같이 사용하면 된다.
query.from(item).where(item.isExpensive(3000)).list(item);


이렇게 QueryDSL을 간단히 살펴보았다.
QueryDSL을 잘 사용하면 복잡한 동적 쿼리를 코드를 통해 안전하게 작성할 수 있으니 잘 사용하길 바란다.

이 다음으로는 네이티브 SQL에 대해 알아보겠다.


3. 네이티브 SQL

JPQL이 대부분의 표준 SQL 기능들을 제공하지만, 특정 데이터베이스에 종속적인 기능은 지원하지 않는다.
특정 데이터베이스에 종속적인 기능을 지원하는 방법은 아래와 같다.

특정 데이터베이스만 사용하는 함수

  • JPQL에서 네이티브 SQL 함수 호출을 통해 사용
  • 하이버네이트의 경우 데이터베이스 방언에 각 데이터베이스에 종속적인 함수들을 정의 해 두었음

특정 데이터베이스만 지원하는 SQL 쿼리 힌트

  • 하이버네이트를 포함한 몇몇 JPA 구현체들이 지원함

인라인 뷰, UNION, INTERSECT

  • 하이버네이트는 지원 하지 않으며 일부 JPA 구현체들이 지원함

스토어 프로시저

  • JPQL을 통해서 스토어드 프로시저를 호출할 수 있음

특정 데이터베이스만 지원하는 문법

  • 오라클의 CONNECT BY 처럼 특정 데이터베이스에 너무 종속적인 SQL 문법은 지원하지 않음. 이때는 네이티브 SQL을 사용해야 함.

위의 경우 등 다양한 이유로 JPQL을 사용하기 적합하지 않을 때, JPA는 SQL을 직접 사용할 수 있는 네이티브 SQL 을 지원한다.

물론 네이티브 SQL을 사용하면 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.

✏️ 네이티브 SQL 사용

  1. 엔티티 조회
String sql = "SELECT ID, AGE, NAME FROM MEMBER WHERE AGE > ? " //sql 정의
  Query nativeQuery = em.createNativeQuery(sql, Member.class).setParameter(1,20);
  List<Member> resultList = nativeQuery.getResultList();
  

예시 코드를 보면 알 수 있듯이 사용방법이 그렇게 어렵지 않다.

  1. 값 조회
String sql = "SELECT ID, AGE , NAME FROM MEMBER";
  
  Query nativeQuery = em.createNativeQuery(sql);
  List<Object[]> resultList = nativeQuery.getResultList();
  for(Object[] row : resultList_ {
	System.out.println("id = " +row[0]);
    System.out.println("age = " +row[1]);
  	//...이하 생략
  }

앞선 코드들과 크게 다르지 않다.
스칼라 타입을 조회했기에 결과를 영속성 컨텍스트가 관리하지 않는다는 것만은 유의하자.

  1. 결과 매핑 사용

매핑이 복잡해지면 결과 매핑을 사용해야 한다.
결과 매핑을 정의할 때는 @SqlResultSetMapping 을 사용하면 된다.

//결과 매핑 정의
  @Entity
  @SqlResultSetMapping (name =  "memberWithOrderCount",
  		entities = {@EntityResult (entityClass = Member.class)},
  		columns = {@ColumnResult (name = "ORDER_COUNT")}
  )
  public class MEmber {...}
  
  //결과 매핑 사용
 
  String sql = 
  	"SELECT M.ID , AGE , NAME, TEAM_ID , I.ORDER_COUNT " +
  	"FROM MEMBER M " +
  	"LEFT JOIN " +
  	" 	(SELECT IM.ID, COUNT(*) AS ORDER_COUNT " +
  	"	FROM ORDERS O, MEMBER IM " +
  	"	WHERE O.MEMBER_ID = IM.ID) I " +
  	"ON M.ID = I.ID";
  
  Query nativeQuery = em.createNativeQuery(sql, "memberWithOrderCount");
  

entities 와 columns 에서 알 수 있듯이 여러 엔티티와 여러 컬럼을 매핑할 수 있다.
결과 매핑 어노테이션은 아래에 정리해 두었으니 필요한대로 사용하면 된다.

SqlResultSetMapping 속성

속성기능
name결과 매핑 이름
entities@EntityResult를 사용해서 엔티티를 결과로 매핑
columns@ColumnResult를 사용해서 컬럼을 결과로 매핑

@EntityResult 속성

속성기능
entityClass결과로 사용할 엔티티 클래스를 지정
fields@FieldResult을 사용해서 결과 컬럼을 필드와 매핑
discriminatorColumn엔티티의 인스턴스 타입을 구분하는 필드(상속에서 사용)

@FieldResult 속성

속성기능
name결과를 받을 필드명
column결과 컬럼명

@ColumnResult 속성

속성기능
name결과 컬럼명

✏️ Named 네이티브 SQL

네이티브 SQL도 Named 네이티브 SQL을 이용해 정적 SQL을 작성할 수 있다.
사용 방법은 간단하다. Named 네이티브 SQL을 등록하고, 사용하면 된다.

//등록
  
 @Entity
 @NamedNativeQuery(
  	name ="Member.memberSQL",
  	query="SELECT ID, AGE FROM MEMBER WHERE AGE >?",
  	resultClass = Member.class
  //원하는 경우 resultSetMapping = "memberWithOrderCount"처럼 결과 매핑도 사용 가능
  )
 public class Member {...}
  
//사용 (TypeQuery 사용 가능)
  TypedQuery<Member> nativeQuery =
  	em.createNamedQuery("Member.memberSQL", Member.class)
  		.setParameter(1,20);

아래에 속성을 정리해 두었으니 필요한 것을 적절히 사용하면 된다.

속성기능
name네임드 쿼리 이름 (필수)
querySQL 쿼리(필수)
hints벤더 종속적인 힌트(하이버네이트 같은 JPA 구현체에 제공하는 힌트)
resultClass결과 클래스
resultSetMapping결과 매핑 사용

✏️ 네이티브 SQL XML에 정의

네이티브 SQL은 보통 JPQL로 작성하기 어려운, 복잡한 쿼리를 작성할 때 사용한다.
이런 경우 자바는 멀티 라인 문자열을 지원하지 않기에 XML에 등록하여 사용하는 것이 편하다. XML의 경우 SQL 개발 도구에서 완성한 SQL을 바로 붙여 넣을 수 있기 때문이다.

<entity-mappings ..>
  	<named-native-query name="Member.memberWithOrderCountXml"
       result-set-mapping="memberWithOrderCountResultMap">
      <query><CDATA[
              SELECT M.ID .....생략..
                     ]></query>
        </named-native-query>
     
      <sql-result-set-mapping name="memberWithOrderCountResultMap">
        <entity-result entity-class="jpabook.domain.Member"/>
        <column-result name="ORDER_COUNT"/>
      </sql-result-set-mapping>
 </entity-mappings>

네이티브 SQL을 난발하면 특정 데이터베이스에 종속적인 쿼리가 증가해서 이식성이 떨어지므로, 마지막 방법으로 사용하는 것이 좋다.
네이티브 SQL로도 해결이 안 된다면 MyBatis나 JdbcTemplate같은 SQL 매퍼와 JPA를 함께 사용하는 것도 고려 해 보자.


4. 스토어드 프로시저

입력 값을 두 배로 증가시켜 주는 proc_multiply 라는 스토어드 프로시저를 JPA로 사용해보자.
이 프로시저는 첫 번째 파라미터로는 값을 입력받고, 두 번째 파라미터로 결과를 반환한다.

DELEMITER //

CREATE PROCEDURE proc_multiply (INOUT inParam INT, INOUT outParam INT)
BEGIN
  SET outParam = inParam * 2;
END //

이 간단한 프로시저를 JPA에서는 아래 코드처럼 호출 가능하다.

StoredProcedureQuery spq =
  em.createStoredProcedureQuery ("proc_multiply");
spq.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN);
spq.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT);
  //registerStoredProcedureParameter를 이용해 프로시저에 사용할 파라미터를 순서, 타입, 파라미터 모드 순으로 정의
 
spq.setParameter(1, 100);
spq.execute();
  
Integer out = (Integer)spq.getOutputParameterValue(2); // 결과 200

만약 자주 호출 할 스토어드 프로시저의 경우 Named 스토어드 프로시저를 사용해서 간편하게 호출 할 수 있다.

@NamedStoredProcedureQuery(
  name = "multiply"
  procedureName = "proc_mutiply",
  parameters = {
  	@StoredProcedureParamter(name = "inParam", mode=
  		ParameterMode.IN, type=Integer.class),
  	@StoredProcedurePatameter(name ="outParam", mode=
  		ParameterMode.OUT, type= Integer.class)
  }
)
@Entity
public class Member {...}
  
  
//사용
StoredProcedureQuery spq = em.createNamedStoredProcedureQuery("multiply");
spq.setParameteR("inParam",100);
spq.execute();

Integer out = (Integer) spq.getOutputParameterValue("outParam");

5. 객체 지향 쿼리 심화

이번에는 고급 주제를 살펴 볼 것이다.
한 번에 여러 데이터를 수정할 수 있는 벌크 연산과 JPQL과 영속성 컨텍스트, JPQL과 플러시 모드에 대해 다루겠다.

✏️ 벌크 연산

엔티티를 수정하려면 변경 감지 기능이나 병합을 사용하고, 삭제하려면 EntityManager.remove() method를 사용해야 하는데 수 백개 이상의 엔티티를 하나씩 처리하기엔 시간이 너무 오래 걸린다.
이때 벌크 연산을 사용하면 된다.
다음 예시는 재고가 10개 미만인 모든 상품의 가격을 10% 상승시키는 예시이다.

String qlString =
  "update Product p " +
  "set p.price = p.price * 1.1 " +
  "where p.stockAmout < :stockAmout";

int resultCount = em.createQuery(sqlString)
    				.setParameter("stockAmount",10)
    				.executeUpdate();

벌크 연산은 executeUpdate 를 사용하면 된다.

다만 , 벌크 연산을 사용할 때 주의해야 할 점이 있다. 😱
벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리를 날린다.

다음과 같은 상황을 생각해보자.

  1. 상품A 를 조회 ( 이때 상품 A의 가격은 1000원)
  2. 벌크 연산으로 모든 상품 가격을 10% 상승시킴
  3. 이때 미리 조회한, 영속성 컨텍스트에 저장된 상품 A의 가격을 조회하면 얼마일까?

이 상황에서 상품 A의 가격은 그대로 1000원으로 나온다.
벌크 연산은 영속성 컨텍스트를 무시하기 때문이다.
이 문제를 해결하려면 벌크 연산을 수행한 직후에 em.refresh() 를 사용해 상품 A를 다시 데이터베이스에서 조회하거나,
벌크 연산을 먼저 실행하고 조회하면 해결된다.
혹은 벌크 연산 수행 후 영속성 컨텍스트를 초기화 하여 영속성 컨텍스트에 남아 있는 엔티티를 제거하는 것도 하나의 방법이다.

✏️ 영속성 컨텍스트와 JPQL

JPQL로 엔티티를 조회하면 영속성 컨텍스트에 관리되고, 임베디드 타입, 값 타입 등을 조회하면 관리되지 않는다.
이때 영속성 컨텍스트에 회원 1이 이미 있는데 JPQL로 회원 1을 다시 조회하면 어떻게 될까?

JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있으면 JPQL로 데이터베이스에서 조회한 결과를 버리고, 대신에 영속성 컨텍스트에 있던 엔티티를 반환한다.

왜 굳이 새로 조회한 member1를 버리고 영속성 컨텍스트에 있는 기존 엔티티르 반환하는 걸까?

기존 엔티티를 새로 검색한 엔티티로 대체하는 것이 더 합리적여 보일 수도 있으나, 영속성 컨텍스트에 수정 중인 데이터가 사라질 수도 있어 위험하다.
따라서 새로 조회한 것을 버리고, 기존의 것을 반환하도록 한다.

여기서 find() vs JPQL 비교 해 보기
em.find()는 엔티티를 영속성 컨텍스트에서 먼저 찾고, 없으면 데이터베이스에서 찾기 때문에 성능상 이점이 있다.
JPQL은 항상 데이터베이스에 SQL을 실행해서 결과를 조회한다.
따라서 가능하다면 em.find()를 쓰는 것이 성능상 더 좋다.

✏️ JPQL과 플러시 모드

플러시는 영속성 컨텍스트의 변경 내역을 데이터베이스에 동기화하는 것이라고 앞선 장들에서 알아보았다.
플러시는 em.flush() method를 통해 직접 호출되거나, 플러시 모드에 따라 커밋 직전 혹은 쿼리 실행 직전에 자동으로 호출된다.

쿼리와 플러시 모드
JPQL은 영속성 컨텍스트를 고려하지 않기에 JPQL을 사용할 땐 플러시 모드를 생각해야 한다.

em.setFlushMode (FlushModeType.COMMIT); //커밋 시에만 플러시
    
product.setPrice(2000); //영속성 컨텍스트에 있는 product의 가격을 변경

// 1. em.flush() 직접 호출
    
//가격이 2000원인 상품을 조회
em.createQuery("select p from Product p where p.price = 2000", Product.class)
  //  .setFlushMode(FlushModeType.AUTO) 2. setFlushMode() 설정
    .getSingeResult();

위의 코드에서 product 가 조회되게 하려면,
em.flush를 직접 호출하거나, FlushModeType을 AUTO로 변경 해 주어야 한다.
플러시 모드의 기본 값은 AUTO로 설정되어 있어 일반적으론 방금의 상황을 고려하지 않아도 된다.
그런데 플러시 모드 최적화를 위해서 COMMIT 모드를 사용해야 할 때가 있다.
그럴 경우에는 주의해서 사용하자.

+ 플러시 모드와 최적화
COMMIT 모드를 사용하면 앞선 예시처럼, 데이터 무결성에 심각한 피해를 줄 수도 있다.
하지만 플러시가 너무 자주 일어나는 상황에서 COMMIT 모드를 사용하면 플러시 횟수를 줄여 성능을 최적화 할 수도 있다.

예시
등록()
쿼리() //플러시
등록()
쿼리() //플러시
커밋() //플러시

AUTO의 경우 쿼리와 커밋할 때 총 3번 플러시 하고,
COMMIT의 경우 커밋 시에만 1번 플러시 한다.

JDBC를 직접 사용해서 SQL을 실행할 때도 플러시 모드를 고민해야 한다.
JPA를 통하지 않고 쿼리를 실행하면 JPA는 실행한 쿼리를 인식할 방법이 없기 때문에, JDBC로 쿼리를 실행하기 직전에 em.flush()를 통해 영속성 컨텍스트의 내용을 데이터베이스에 동기화하는 것이 안전하다.


길고 긴 이번 시간이 끝이 났다.

다음 시간에는 스프링 데이터 JPA에 대해 알아보겠다.

참조 : 자바 ORM 표준 JPA 프로그래밍 -김영한

profile
안녕하세요 개발하는 사람입니다.

0개의 댓글

관련 채용 정보