Open Session IN View : 하이버네이트
향후에 JPA 생성되면서, Open EntitiyManager In View로 바뀌면서 관례상 OSIV로 불리움.
WARN 6208 --- [ main]
JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open
in-view is enabled by default. Therefore, database queries
may be performed during view rendering. Explicitly configure
spring.jpa.open-in-view to disable this warning
spring.jpa.open in-view
: TRUE 기본값
OSIV전략은 데이터베이서 커넥션 시작 시점부터 API 응답이 끝날 때까지 영속성 컨텍스트와 데이터베이스 커넥션을 유지함.
장점: View Template이나 API 컨트롤러에서 지연 로딩이 가능.
단점: 너무 오랜시간 데이터베이스 커넥션 가지고있으면 실시간 트래픽이 중요한 어플리케이션에선 문제가 될 수 있음 -> 커넥션이 말라버림
spring.jpa.open in-view
: FALSE
OSIV를 끄면 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환
장점:
단점:
OSIV를 끈 상태로 복잡성을 관리하는 방법: Command 와 Query 분리
OrderService //서비스 계층에서 트랜잭션 유지
고객 서비스의 실시간 API는
OSIV를 끄고
, ADMIN 처럼 커넥션을 많이 사용하지 않는 곳에서는OSIV를 켠다
.
https://spring.io/projects/spring-data-jpa
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import java.util.List;
@Repository //자동으로 spring bean으로 repository 등록, component scan의 대상
@RequiredArgsConstructor
public class MemberRepository {
/*
@PersistenceContext
private EntityManager em;*/
private final EntityManager em;
public void save(Member member){ //회원 저장
em.persist(member);
}
public Member findOne(Long id){ //회원 단건 조회
return em.find(Member.class, id);
}
public List<Member> findAll(){ //회원 리스트 조회
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public List<Member> findByName(String name){ //회원을 조회하는데 이름에 의해서 조회
return em.createQuery("select m from Member m where m.name = :name", Member.class ) //JPQL에 의해
.setParameter("name",name)
.getResultList();
}
}
package jpabook.jpashop.repository;
import jpabook.jpashop.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByName(String name);
}
findByName
: select m from Member m where m.name =?
자동 생성
select m from Member m where [m.name](http://m.name/) = :name
실무에서는 조건에서 실행되는 쿼리가 달라지는 동적쿼리 많이 사용
JPQL을 자바코드로 작성할 수 있게 해줌.
JPA Criteria 사용이 가능하지만, 유지보수가 어려움.
/**
* JPA Criteria
*/
public List<Order> findAllByCriteria(OrderSearch orderSearch) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> o = cq.from(Order.class);
Join<Object, Object> m = o.join("member", JoinType.INNER);
List<Predicate> criteria = new ArrayList<>();
//주문 상태 검색
if (orderSearch.getOrderStatus() != null) {
Predicate status = cb.equal(o.get("status"), orderSearch.getOrderStatus());
criteria.add(status);
}
//회원 이름 검색
if (StringUtils.hasText(orderSearch.getMemberName())) {
Predicate name =
cb.like(m.<String>get("name"), "%" + orderSearch.getMemberName() + "%");
criteria.add(name);
}
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
...
plugins {
id 'org.springframework.boot' version '2.6.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
// querydsl 플러그인 추가
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
implementation 'mysql:mysql-connector-java'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// querydsl 디펜던시 추가
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}
...
// querydsl 사용할 경로 지정합니다. 현재 지정한 부분은 .gitignore에 포함되므로 git에 올라가지 않습니다.
def querydslDir = "$buildDir/generated/'querydsl'"
// JPA 사용여부 및 사용 경로 설정
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
// build시 사용할 sourceSet 추가 설정
sourceSets {
main.java.srcDir querydslDir
}
// querydsl 컴파일 시 사용할 옵션 설정
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
// querydsl이 compileClassPath를 상속하도록 설정
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
gradle 7.x버전 이용 시 -> build.gradle 작성시, 오류 해결!
OrderRepository
public List<Order> findAll(OrderSearch orderSearch){
QOrder order = QOrder.order;
QMember member = QMember.member;
//이전이랑 비교안되게 JPA랑 비슷하게 직관적임!
//컨디션도 넣을 수 있음
//컴파일 시점에 오타가 잡힌다는 큰 장점도 있음!!
JPAQueryFactory query = new JPAQueryFactory(em);
return query
.select(order)
.from(order)
.join(order.member, member)
.where(statusEQ(orderSearch.getOrderStatus()), member.name.like(orderSearch.getMemberName()))
.limit(1000)
.fetch();
}
//멤버네임 동적쿼리로 짜기
private BooleanExpression nameLike(String nameCond) {
if (!StringUtils.hasText(nameCond)) {
return null;
}
return QMember.member.name.like(nameCond);
}
//status 동적쿼리로 짜기
private BooleanExpression statusEQ(OrderStatus statusCond){
//상태가 널이면 안쓰고 버림
if (statusCond == null){
return null;
}
//아니면 스테이터스 제대로 반환
return QOrder.order.status.eq(statusCond);
}
실무에서는 복잡한 동적 쿼리를 많이 사용하게 되는데, 이때 Querydsl을 사용하면 높은 개발 생산성을 얻으면서 동시에 쿼리 오류를 컴파일 시점에 빠르게 잡을 수 있다.
Querydsl은 JPA로 애플리케이션을 개발 할 때 선택이 아닌 필수라 생각한다.