🚩Validation
읽고 다시 해보기 : https://mangkyu.tistory.com/174
🚩Spring AOP
부가기능 모듈화.
spring aop 는 runtime 에 프록시를 통해 객체에 접근한다.
dependency
implementation("org.springframework.boot:spring-boot-starter-aop") // aop
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy
@SpringBootApplication
class ToDoListApplication
어노테이션 설정
@Target(AnnotationTarget.FUNCTION) // 적용될 대상
@Retention(AnnotationRetention.RUNTIME) // 어느시점까지 사용?
annotation class StopWatch()
AOP 설정
@Aspect
정의할 때. 클래스에 붙여준다.
@Around
적용 시점.
-@Around : 메서드 실행 전후
-@Before : 메서드 호출 전
-@Afrer : 메서드 결과와 관계없이 완료되면 실행
-@AfterReturning : 메서드가 정상적으로 반환 했을 때만
-@Afterthrowing : 메서드가 예외를 발생시킬 때
joinPoint.procced() : 메소드 실행시킴
PointCut Expression : execution(* ...) / within(* ...)
위 코드는, 잘 동작 하지만, 한가지 오류가 있습니다. @StopWatch 어노테이션이 붙은 메소드에서 빈 DTO를 응답하는 문제가 있어요. 이에 대한 해결은, 마지막 테스트 강의에서 다룰 예정입니다.굉장히 간단한 문제이니 직접해결해보셔도 좋습니다!
🚩QueryDSL
: 직관적인 쿼리를 작성하면서 동적인 쿼리 작성 가능, 컴파일 타임에 오류 잡아줌
결과 또한 JPA 의 Entity 로 받을 수 있다.
동적쿼리?
여러 필터 조건 등등 if 문이 길어짐
상황에 따라 형태가 많이 달라진다.
gradle 설정
plugins {
...
kotlin("kapt") version "1.8.22" // 어노테이션을 분석해서 querydsl 에 알려줌.
...
}
val queryDslVersion = "5.0.0" // 버전 명시
dependencies {
...
implementation("com.querydsl:querydsl-jpa:$queryDslVersion:jakarta")
kapt("com.querydsl:querydsl-apt:$queryDslVersion:jakarta")
...
}
=> Entity 를 추가하거나 변경할 때 compileKotlin 으로 QClass 업데이트 할 것
❗QueryDSL의 문법
QueryDsl을 컴파일하면 자동으로 Entity 를 기반으로 한 QClass 가 생성되고 이를 이용한다.
val post = QPost.post // 기본 instance
val post = QPost("p") // 별칭 지정
그러면 queryfactoy 에서 post 를 호출할 수 있다.
● 기본 조회
queryFactory.select(post).from(post).fetch() // 리스트 조회
queryFactory.select(post).from(post).fetchFirst() // 단건 조회 (Not Null)
queryFactory.select(post).from(post).fetchOne() // 단건 조회 (Nullable)
● 검색 조건
queryFactory.selectFrom(post)
.where(post.title.eq("a").and(post.view.gt(10).or(post.author.like("%a%"))
.fetch()
● 정렬
queryFactory.selectFrom(post).orderBy(post.createdAt.desc()).fetch()
● 페이징 - offset, limit
queryFactory.selectFrom(post)
.where(post.title.contains("a))
.orderBy(post.createdAt.desc())
.offset(10)
.limit(20)
● 카운트 - count() , Wildcard.count
// SELECT COUNT(post.id) FROM post
val count1: Long? = queryFactory
.select(post.count())
.from(post)
.fetchOne()
// SELECT COUNT(*) FROM post
val count2: Long? = queryFactory
.select(Wildcard.count)
.from(post)
.fetchOne()
assert(count1 == count2)
조인
: 둘 이상의 테이블을 연결해서 데이터를 검색하는 방법
적어도 하나의 컬럼을 공유하고 있어야 한다!!
● 연관관계 조인
val comment = QComment.comment
// 기본 조인 (innerJoin과 동일)
queryFactory.select(post)
.from(post)
.join(post.commet, comment)
.where(comment.content.contains("아니"))
.fetch()
// LEFT JOIN
queryFactory.select(post)
.from(post)
.leftJoin(post.commet, comment)
.where(comment.content.contains("아니"))
.fetch()
// RIGHT JOIN
queryFactory.select(post)
.from(post)
.rightJoin(post.commet, comment)
.where(comment.content.contains("아니"))
.fetch()
// FETCH JOIN - 뒤에서 설명 예정
queryFactory.select(post)
.from(post)
.leftJoin(post.commet, comment)
.fetchJoin()
.where(comment.content.contains("아니"))
.fetch()
inner join
: 공통된 것만
right join
: 조인 기준 오른쪽에 있는 것은 다
left join
: 조인 기준 왼쪽에 있는 것은 다
● 연관관계 X 조인
val user = QUser.user
queryFactory.select(post)
.from(post)
.join(user).on(post.authorName.eq(user.name))
.fetch()
post 에서 불러올 필요 X, on 절을 기반으로 가져옴
● 서브쿼리 - JPAExpression
val p = QPost("postSub")
queryFactory.select(post)
.from(post)
.where(post.numLike.eq(
JPAExpressions.select(p.numLike.max()).from(m))
)
.fetch()
쿼리 안의 쿼리?
● 동적 쿼리에의 활용
-Boolean builder
fun serarchUser(email: String?, nickname: String?): List<User> {
val builder = new BooleanBuilder()
email?.let { builder.and(user.email.contains(it)) }
nickname?.let { builder.and(user.nickname.contains(it) }
return queryFactory.selectFrom(user)
.where(builder)
.fetch()
}
BooleanBuilder() 객체 생성해서 조건마다 추가해준다. 그리고 Where 절에 인자로 넣어준다.
● QueryProjection
: Entity 가 아닌 다른 타입으로 반환받을 수 있다.!!
-Projections.constructor
data class UserDto( val id: Long, val email: String )
val userDtos = queryFactory
.select(Projections.constructor(
UsertDto::class.java,
user.id, // 순서 중요!
user.email // 순서 중요!
))
.from(user)
.fetch()
생성자 사용 =>객체 파라미터 순서 주의
● @QueryProjection
data class UserDto @QueryProjection constructor( val id: Long, val email: String)
val userDtos = queryFactory
.select(QUserDto(
id = user.id,
email = user.email
))
.from(user)
.fetch()
데이터 클래스 생성자 앞에 어노테이션 붙여줌으로써 해당 클래스의 Q클래스를 생성
● QueryDsl 벌크 수정, 삭제
: queryFactory 에서 수정, 삭제
모든 Entity 에서 변경사항을 감지해야하는 JPA 의 Dirty Checking 에 비해 월등히 개선된 성능
BUT 영속성 컨텍스트 내의 Entity 와 별개로 실행되는 것이라서 이 쿼리를 실행한 후의 영속성 컨텍스트와 DB의 상태가 서로 다르게 된다 때문데 쿼리를 실행 후 반드시 em.clear() 를 통해 영속성 컨텍스트 초기화를 해주어야 한다.
val updatedCount = queryFactory
.update(user)
.set(user.nickname, "TEST_NICKNAME")
.where(user.id.lt(10L))
.execute()
val updatedCount = queryFactory
.delete(user)
.where(user.id.lt(10L))
.execute()
❗Query Factory 만들자!
class QueryDslSupport {
@PersistenceContext
protected lateinit var entityManager: EntityManager
protected val queryFactory: JPAQueryFactory
get() {
return JPAQueryFactory(entityManager)
}
}
@PersistenceContect
: JPA 엔티티 매니저 주입
get()
: JPAQueryFactory 를 생성할 때 엔티티 매니저를 사용하여 인스턴스를 반환
❓왜 queryFacotry 를 불러올 때 그냥 불러와서 사용하는게 아니고 매번 초기화해서 앤티티매니저를 주입받는거지?
jpa 엔티티매니저의 트랜잭션 내에서 동작하도록, 여러 개의 앤티티매니저가 있을 경우 명시적으로 지정해서 일관성을 보장
'왕초보일지' 카테고리의 다른 글
240207 TIL | ObjectMapper (0) | 2024.02.07 |
---|---|
240206 TIL | (1) | 2024.02.06 |
복습과제 (0) | 2024.02.02 |
240201 TIL | ubuntu 환경에 docker 설치하고 실행하기 (0) | 2024.02.01 |
240130 TIL | (1) | 2024.01.30 |