QueryDsl like 와 contains 의 차이



QueryDsl 을 사용하다가 like 기능이 필요한 일이 있었습니다. 



query.where(qTest.testName.like(searchOptions.getTestName()));



위와 같이 코드를 사용하고 테스트를 하니 원하는 결과가 나오지 않았습니다.

그래서 검색과 코드를 들어가서 살펴본 결과 이유를 알 수 있었습니다.


위의 like 는 완전히 일치하는 경우를 찾을 때 사용합니다.



query.where(qTest.testName.contains(searchOptions.getTestName()));



위의 contains 는 Mysql 에서의 like 처럼 일부를 통해 검색할 때 사용합니다.




'Database > QueryDsl' 카테고리의 다른 글

QueryDsl 사용하기.  (0) 2018.08.13



QueryDsl 사용하기.



이번에 QueryDsl 을 사용할 기회와 필요성이 있어 적용하고 사용해봤습니다.

그 과정이 쉽지는 않다는 것을 느꼈기에 정리하고자 합니다.


우선 QueryDsl 을 사용하기 위해 dependency 를 추가할때 어떤 글은 com.querydsl 이고 어떤 글은 com.mysema.querydsl 으로 나와있습니다.

이유는 QueryDsl 이 4.x 버전으로 올라가며 package 명이 바뀌어서 그 이전글은 그대로 남아 있는 것으로 생각됩니다.


처음으로 build.gradle 파일에 아래와 같이 내용을 써줍니다.



dependencies {
compile("mysql:mysql-connector-java")
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile ("com.querydsl:querydsl-core")
compile ("com.querydsl:querydsl-apt")
compile ("com.querydsl:querydsl-jpa")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

sourceSets {
main {
java {
srcDirs 'src/main/java', 'src/main/generated'
}
}
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
file(new File(projectDir, "/src/main/generated")).deleteDir()
file(new File(projectDir, "/src/main/generated")).mkdirs()
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
options.compilerArgs = [
"-proc:only",
"-processor", "com.querydsl.apt.jpa.JPAAnnotationProcessor",
"-processor", 'com.querydsl.apt.jpa.JPAAnnotationProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor'
]
destinationDir = file('src/main/generated')
}

compileJava {
dependsOn generateQueryDSL
}

clean.doLast {
file(new File(projectDir, "/src/main/generated")).deleteDir()
}



위에서 clean.doLast 라는 부분이 의아하실 것 같은데 컴파일할 때 이전에 생성되었던 Q Class 가 남아있다면 에러를 발생시키기 때문에 이를 방지하기 위해 clean.doLast 에서 지우고 있습니다.





위 사진에 보이는 Tasks - application - bootRun 을 실행하면 





위와 같이 지정한 위치에 안에는 Q Class 들이 들어가 있는 상태의 generated 폴더가 생성됩니다.

이젠 CustomRepository 와 CustomRepositoryImpl 을 만들어야 합니다.



public interface CustomTestRepository {
Page<Test> findTestBySearchFilter(Long testId, SearchOptions searchOptions, Pageable pageable);

}



@Component
public class TestRepositoryImpl extends QueryDslRepositorySupport implements CustomTestRepository {

public TestRepositoryImpl() {
super(Test.class);
}

@Override
public Page<Test> findTestBySearchFilter(Long testId, SearchOptions searchOptions, Pageable pageable) {
QTest qTest = QTest.test;
JPQLQuery query = from(qTest);

if(testId != null) {
query.where(qTest.testId.eq(testId));
}

do something...

final List<Test> tests = getQuerydsl().applyPagination(pageable, query).fetch();
final long totalCount = query.fetchCount();

return new PageImpl<>(tests, pageable, totalCount);
}



위의 코드는 예시로 필요에 따라 추가, 수정하여 사용하면 됩니다.





'Database > QueryDsl' 카테고리의 다른 글

QueryDsl like 와 contains 의 차이  (0) 2018.08.13



JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프



 JPA를 사용해 개발을 하던 도중 낯선 에러를 보게 되었습니다.



Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain:



JsonMapping 을 하던 도중 무한 순환을 하여 StackOverflow 에러가 났다는 에러였습니다.

뭔가 하고 찾고 분석한 결과 원인을 알아내고 수정할 수 있었습니다.

문제의 컨트롤러에서 아래와 같이 ResponseEntity 를 리턴타입으로 가지고 있었습니다.



public ResponseEntity<?> searchOrders() {



return ResponseEntity.ok(new ApiResponse(123, "Success", entityObject));



Http Response body 에 object 를 serialized 할 때 jackson 을 사용하는데 jackson 이 데이터를 뽑을 때 getter 기준으로 뽑아낸다고 합니다.

그런데 제가 한 실수가 일단 성공하고 return 이 잘되는지 확인을 먼저 하려는 마음에 사용하던 Entity 객체를 return 하였는데 

이 Entity가 OneToMany 로 자식 엔티티를 참조하고 자식 Entity는 ManyToOne 으로 부모 Entity를 참조하고 있는 상태였습니다.



@Entity
@Table(name = "test")
public class Test {
    

...


@OneToMany(mappedBy = "test")
private List<SubTest> subTests = new ArrayList<>();
}



@Entity
@Table(name = "biz_order_products")
public class BizOrderProduct {

...


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "testId")
private Test test;

}



그래서 양방향 참조를 하는 관계에서 getter 를 하게 되니 서로 참조하고 있고 get 을 하면 순환을 하여 무한루프에 빠지게 되고 이 데이터가 메모리의 Stack 영역에 계속해서 쌓이게 되어 메모리의 Heap 영역을 침범하게 되는 StackOverflow 에러가 발생하게 된 것 이였습니다.


이를 해결하기 위해선 


1. 새로운 객체를 만들어 이를 return.

2. @JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "순환이 일어나는 참조객체에 맵핑되는 컬럼") 으로 순환을 끊기.

3. 양방향 참조를 단방향 참조로 만들기.

4. fetch join을 사용하기.


가 있습니다.

해결 방법이 더 있을 수 있다면 댓글로 알려주시면 감사하겠습니다.





'Database > JPA & JPQL' 카테고리의 다른 글

[JPA] Id Annotation과 return cached data  (0) 2018.07.05
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18



[JPA] Id Annotation과 return cached data


JPA로 Query method를 작성해 사용하던 도중 이상함을 느꼈습니다.



List<UserTrack> findAll();



예를 들어 위와 같은 Query method를 사용하여 쿼리를 작성하여 사용했는데 이상하게 같은 데이터를 중복, 반복해서 return 하는 현상이 일어났습니다.

해당 문제는 Entity에서 @Id Annotation을 건 컬럼이 DB Table에서 유니크한 값을 가지고 있지 않은 경우에 발생합니다.



@Entity
public class UserTrack {
@Id
private Long age;

private String name;
}



만약 위와 같이 @Id Annotation을 걸었는데 테이블에서 해당 컬럼의 데이터가 중복될 경우 애매하여 이전에 찾아서 Cache 되었던 data가 있는지 찾습니다.


이를 해결하려면 


1. 해당 테이블의 PK에 Id Annotation을 겁니다.

2. PK가 없을 경우에는 EmbeddedId 를 만들어 복합키로 유니크한 Id를 만들어 줍니다.


이렇게 하면 제대로 select 해서 return 하는 것을 확인할 수 있습니다.



'Database > JPA & JPQL' 카테고리의 다른 글

JPA JsonMappingException: Infinite recursion (StackOverflowError) - 무한루프  (0) 2018.08.02
Pageable로 Limit 걸기  (0) 2018.06.23
JPQL이란?  (0) 2018.06.18



Pageable로 Limit 걸기


JPA, JPQL 을 사용하다가 Limit이 필요한 경우가 생겼었습니다.


JPQL에 limit을 사용하니 에러가 발생하여 찾아보니 JPA, JPQL 에서 limit의 기능을 사용하려면 다른 방법을 사용해야 합니다.



Pageable limitOneHundred = new PageRequest(startPosition, 100);
List<SelectAll> tempList = requestRepository.findAllByDate(startDate, targetDate, limitOneHundred);

@Query(value = "SELECT n " +
"FROM LogRequest n " +
"WHERE DATE(n.regDate) >= :date " +
"AND DATE(n.regDate) = :targetDate " +
"ORDER BY n.no")
public List<SelectAllNaver> findAllByDate(@Param("date") Date date,
@Param("targetDate") Date targetDate,
Pageable limitOneHundred);



Pageable 을 사용하면 limit을 사용하는 것처럼 범위를 지정하여 페이징을 할 수 있습니다.

혹은 아래와 같이 Query annotation의 nativeQuery 옵션을 true로 하여 limit을 사용하는 방법도 있습니다.



@Query(nativeQuery = true, value = "select * from SLSNotification s where s.userId = :userId ORDER BY snumber desc LIMIT 20")
List<SLSNotification> getUserIdforManage(@Param("userId") String userId);




참조.

http://www.baeldung.com/spring-data-jpa-query

https://stackoverflow.com/questions/47616482/how-to-limit-result-in-query-used-in-spring-data-repository



JPQL이란?


JPQL(Java Persistence Query Language) 는 JPA (Java Persistence API) 의 일부로 정의된 플랫폼 독립적인 객체지향 쿼리 언어 입니다.


JPQL 은 관계형 데이터베이스의 엔티티에 대한 쿼리를 만드는데 사용됩니다.


SQL 에 크게 영향을 받아 SQL 문과 비슷하지만 데이터베이스의 테이블에 직접 연결되는 것이 아니라 JPA 엔티티에 대해 동작합니다.


그래서 JPQL 의 쿼리에는 테이블이 아닌 엔티티에서 표현하고 있는 컬럼의 이름을 써줘야 합니다.


아래의 Spring을 통한 예시에 나와있습니다.



@Entity
public class UserStatistics {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String host;

private String href;

private String pathName;

private String referrerHost;

private String referrerPath;

private Long visitCount;

private LocalDate date;
}



@Query(value = "SELECT u.pathName as pathName, DATE(u.date) as date, u.visitCount as visitCount " +
"FROM UserStatistics u GROUP BY DATE(u.date), u.pathName, u.visitCount")
List<UserStatisticsDateCount> findVisitCountGroupByDate();




참조

https://en.wikipedia.org/wiki/Java_Persistence_Query_Language

http://www.baeldung.com/spring-data-jpa-query





+ Recent posts