jpa-spring boot - api OSIV

김강현·2023년 4월 17일
0

SPRING-JPA-실습-API

목록 보기
4/4

OSIV 와 성능 최적화

  • Open Session In View : 하이버네이트
  • Open EntityManger in View : JPA
    (관례상 OSIV 라 함)

open in view - true

spring.jpa.open-in-view true 가 기본값

true 일때는, transaction 이 끝나더라도 영속성 컨텍스트를 유지하고 있음. (데이터 베이스 커넥션을 유지)
화면이 렌더링 될때까지 혹은, Api 값이 반환 될때까지!

그래야, transaction 사용하는 서비스 계층 외부에서 사용되는 것들에서 활용이 가능하기 때문에!!

Lazy 로딩 을 할 수 있는 등, 데이터베이스 커넥션을 유지해준다!!

그런데 이 전략은... 오랜시간동안 데이터베이스 커넥션 리소스를 사용하기 때문에, 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄 수 있음!! -> 이는 장애로 이어질 수 있다.
만약 api Controller 에서 외부 API 를 반환하게 되면, 그 시간 만큼 커넥션 리소스를 붙잡고 있게 되고, 이는 리소스의 부족을 야기!!

open in view - false

데이터 베이스 커넥션 리소스를 낭비하지 않음!
하지만 lazy 로딩 같은 기능들은 트랜잭션 내 에서 해결이 되어야함!
트랜잭션 외부에서 쉽게 사용하던 것들을 트랜잭션 내부로 옮겨야함

기존에 lazy 로딩으로 잘 되던것들이, proxy 객체로 넘어오게 됨! (에러)

open in view - false 기법

OrderQueryService 만들기

이 서비스 계층에서 모든 걸 처리한 뒤에,
Controller 로 내보내기!!

이런식으로 따로 만들어서, 최종적으로 데이터가 다 담겨 있는 데이터를 넘겨주기!!

커맨드와 쿼리를 구분하는 것이 좋다

  • 커맨드 : C, U, D
  • 쿼리 : Read (조회)

보통의 성능 문제는 쿼리에서 일어남!!
이 둘을 분리하는 것은 유지보수 관점에서 충분히 의미 있음.

  • OrderService: 핵심 비즈니스 로직
  • OrderQueryService: 화면이나 API에 맞춘 서비스 (주로 읽기 전용 트랜잭션 사용)

고객 서비스의 실시간 API 는 OSIV 를 끄고
ADMIN 사이트 처럼 커넥션을 많이 사용하지 않는 곳에서는 OSIV 를 켠다.

스프링 데이터 JPA

기본적으로 중복되는 메소드들을 줄이기 위해, 시작한 프로젝트!!

기존 <MemberRepository.java>

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    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)
                .setParameter("name", name)
                .getResultList();
    }

}

이후 <MemberRepository.java>

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByName(String name);
}

Repository 들이 기본적으로 제공할 것 같은 것들 메소드로 싹 저장 되어 있음!!
심지어 findByName 도 메소드 이름을 보고, 알아서 완성된다!
=> select m from Member m where m.name = :name 으로 jpql 쿼리

다른 계층에서 사용하는 findOne()findById() 로 바꾸어 주면 됨!

// 이전
return memberRepository.findOne(memberId);
// 이후
return memberRepository.findById(memberId).get();

interface 로 해놔도, spring에서 알아서 주입을 해줌!!

QueryDSL

jpql 쿼리를 java 문법으로 바꿈!

세팅

build.gradle

QueryDSL 을 사용하려면 build.gradle 에 dependencies 추가해줘야함

//querydsl 추가
buildscript {
	ext {
		queryDslVersion = "5.0.0"
	}
}

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.10'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'

	//querydsl 추가
	id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}


group = 'jpabook'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-devtools'
	implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.6'
	implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'
	testImplementation 'junit:junit:4.13.1'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

    //querydsl 추가
	implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
	implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

tasks.named('test') {
	useJUnitPlatform()
}
//querydsl 추가
def querydslDir = 'src/main/generated'
//def querydslDir = "$buildDir/generated/querydsl"

querydsl {
	library = "com.querydsl:querydsl-apt"
	jpa = true
	querydslSourcesDir = querydslDir
}
sourceSets {
	main {
		java {
			srcDirs = ['src/main/java', querydslDir]
		}
	}
}
compileQuerydsl {
	options.annotationProcessorPath = configurations.querydsl
}
configurations {
	querydsl.extendsFrom compileClasspath
}

compileQuerydsl

사용

    public List<Order> findAllByQueryDSL(OrderSearch orderSearch){
        QOrder order = QOrder.order;
        QMember member = QMember.member;

        JPAQueryFactory query = new JPAQueryFactory(em);
        
        return query.select(order)
                .from(order)
                .join(order.member, member)
                .limit(1000)
                .fetch();
    }

메소드 체인으로 쉽게 구현 가능!
문자열 에러 걱정할 필요도 없음!!

조건을 걸고 싶을땐


이런 식들로 메소드 안에서 편하게 condition 적용 가능!
(이런건 재사용도 됨)

QueryDSL generate 된 파일들은 .gitignore 에 포함시키기!!

profile
this too shall pass

0개의 댓글