Java - You need to run build with JDK or have tools.jar on the classpath 오류




You need to run build with JDK or have tools.jar on the classpath. 

If this occures during eclipse build make sure you run eclipse under JDK as well 

(com.myseam.maven:apt-maven-plugin:1.1.3:process:default:generate-sources)


만약 위와 같은 에러가 pom.xml에 났다면?






project folder 우클릭 > Properties > Java Build Path > Libraries Tab 에서 JRE System Library를 현재 로컬에 

설치되어 있는 JDK의 패스로 잡아줍니다.


그리고 이클립스 eclipse.ini 파일을 열고 -vm 옵션을 사용해서 실제 JDK가 설치된 경로를 추가합니다.

여기서 -vm 위치가 중요한데 다음과 같이 -vmargs 위에 해당 경로를 추가합니다.


....

-vm

$JDKPATH/bin/javaw.exe

-vmargs

...


이클립스를 재실행하고 Maven> Update Project...를 선택해서 해당 프로젝트를 클릭하고

Update 해줍니다.



posted by 여성게
:
Web/JPA 2019. 2. 11. 23:43

JPA - JPAQuery와 Q클래스


긴 포스팅은 아니지만 예제로 소스를 짜는 도중에 몰랐던 내용을 알게되어서 작성한다. QueryDSL로 예제중 나온 상황이다.



1
2
3
4
5
6
7
8
9
10
public List<MyEntity> findMyDumbEntities(long id) {    
      QMyEntity qMyEntity = QMyEntity.myEntity;
      JPAQuery jpaQuery = new JPAQuery(entityManager);
      MyEntity myFirstEntity = jpaQuery.from(qMyEntity)
                                        .where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
      MyEntity mySecondEntity = jpaQuery.from(qMyEntity)
                                        .where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
 
      return Arrays.asList(myFirstEntity, mySecondEntity);    
}
cs


언뜻 보기에는 크게 문제가 없는 듯 싶다. 하지만 이 소스를 실행하면 이러한 예외가 발생한다.



java.lang.IllegalStateException: qMyEntity is already used


즉, 같은 JPAQuery 객체로 같은 Q타입클래스를 사용할 수없는 것이다. 사실 내부를 까보지는 않아서 원인파악은 해보아야겠지만 일단 결론은 그렇다. 그렇기 때문에 소스가 이런식으로 변경되어야한다.


1
2
3
4
5
6
7
8
9
public List<MyEntity> findMyDumbEntities(long id) {        
          QMyEntity qMyEntity = QMyEntity.myEntity;
          MyEntity myFirstEntity = new JPAQuery(entityManager).from(qMyEntity)
                                    .where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
          MyEntity mySecondEntity = new JPAQuery(entityManager).from(qMyEntity)
                                    .where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
 
          return Arrays.asList(myFirstEntity, mySecondEntity);        
}
cs


이런식으로 JPAQuery 객체를 만드는 메소드를 하나 정의하는 것이 좋을 것같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
 
protected JPQLQuery jpaQuery() {
    return new JPAQuery(entityManager);
}
 
@Override
public List<Article> findAll() {
      return jpaQuery().from(article).fetchAll().list(article);
}
 
public Article findById(@Nonnull final long id) {
      return jpaQuery().from(article).where(article.id.eq(id)).uniqueResult(article);
}
cs

언젠가는 이런 상황이 닥칠수도 있을 것같아서 포스팅한다..

posted by 여성게
:
Web/JPA 2019. 2. 9. 18:59


JPA - Criteria Query(객체지향 쿼리 빌더), JPQL Query Builder Class




Criteria 쿼리는 JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API이다. Criteria를 사용하면 문자가 아닌 코드로 JPQL를 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있고 문자 기반의 JPQL보다 동적 쿼리를 안전하게 생성할 수 있다. 하지만 이번에 Criteria를 간단하게만 다루려고 한다. 이유는 실제 Criteria를 사용해서 개발하다보면 코드가 복잡하고 장황하며 직관적으로 이해하기 힘들다는 단점이 있기때문이다. 다음 포스팅에서는 코드로 쿼리를 작성할 수 있고 간단한 QueryDSL을 다음에 자세히 다뤄볼 예정이기에 이번 포스팅은 몇가지 간단하게만 다뤄본다.





Criteria 기초



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
CriteriaBuilder builder = em.getCriteriaBuilder(); --> 1
//CriteriaBuilder로부터 CriteriaQuery객체를 얻어온다. 
//반환타입을 알수 없다면 제네릭타입을 Object로 준다.
CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class); --> 2
        
//조회의 시작점을 뜻하는 Root객체 생성 여기서 변수명 m은 JPQL에서 별칭이라고 생각하면 된다.
//반환타입을 알수 없다면 제네릭타입을 Object로 준다.
Root<MemberJPQL> m = query.from(MemberJPQL.class); --> 3
//distinct 중복제거.
query.select(m).distinct(true); --> 4
    
//위에서 반환타입이 명확하지 않다면 Query 객체로 쿼리를 만들고 결과를 받는다.
TypedQuery<MemberJPQL> typeQuery = em.createQuery(query);
    
List<MemberJPQL> members = typeQuery.getResultList();
cs

   



    1  

 Criteria 쿼리를 생성하려면 먼저 Criteria 빌더(CriteriaBuilder)를 얻어야 한다. Criteria 빌더는 EntityManager나 EntityManagerFactory에서 얻을 수 있다. 

    2

 Criteria 쿼리 빌더에서  Criteria 쿼리( CriteriaQuery)를 생성한다. 이때 반환 타입을 지정할 수 있다.

    3

 FROM 절을 생성한다. 반환된 값 m은  Criteria에서 사용하는 특별한 별칭이다. m을 조회의 시작점이라는 의미로 쿼리 루트(Root)라고 한다.

    4

 SELECT 절을 생성한다. 


이렇게  Criteria쿼리를 완성하고 나면 다음 순서는 기존의 JPQL과 같다. em.createQuery(query)에 완성된  Criteria쿼리를 넣어주면 된다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
CriteriaBuilder builder = em.getCriteriaBuilder();
    
//CriteriaBuilder로부터 CriteriaQuery객체를 얻어온다. 
//반환타입을 알수 없다면 제네릭타입을 Object로 준다.
CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class);
 
//조회의 시작점을 뜻하는 Root객체 생성 여기서 변수명 m은 JPQL에서 별칭이라고 생각하면 된다.
//반환타입을 알수 없다면 제네릭타입을 Object로 준다.
Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
//검색조건 정의
Predicate usernameEqual = builder.equal(m.get("username"), "여성게1");
//정렬조건 정의
Order ageDesc = builder.desc(m.get("age"));
 
//distinct 중복제거.
query.select(m)
     .distinct(true)
     .where(usernameEqual)
     .orderBy(ageDesc);
 
cs

  


다음으로는 검색조건,정렬조건을 정의하는 부분이다. 위와 같이 where절에 들어가는 부분은 Predicate 클래스로, orderBy에 들어가는 부분은 Order 클래스로 정의해준다. 위와 같이 객체로 정의하여도 되지만


~.where(builder.equal(m.get("username"),"여성게1"); 

-->이런식으로 where절에 바로 넣어주어도 된다.






조회(한건,여러건) - Criteria



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public void select() {
 
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    //CriteriaBuilder로부터 CriteriaQuery객체를 얻어온다. 
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class);
    
    //조회의 시작점을 뜻하는 Root객체 생성 여기서 변수명 m은 JPQL에서 별칭이라고 생각하면 된다.
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    //검색조건 정의
    Predicate usernameEqual = builder.equal(m.get("username"), "여성게1");
    //정렬조건 정의
    Order ageDesc = builder.desc(m.get("age"));
 
    //distinct 중복제거.
    query.select(m)
         .distinct(true)
         .where(usernameEqual)
         .orderBy(ageDesc);
 
    //위에서 반환타입이 명확하지 않다면 Query 객체로 쿼리를 만들고 결과를 받는다.
    TypedQuery<MemberJPQL> typeQuery = em.createQuery(query);
 
    List<MemberJPQL> members = typeQuery.getResultList();
    
    System.out.println("=================select==================");
 
    for(MemberJPQL member : members) {
        System.out.println(member.toString());
    }
 
    System.out.println("=================select==================");
}
 
    
/*
 * 조회대상을 여러건 지정할때
 * ex) select m.name,m.age from MemberJPQL m
 */
 
public void select2() {
 
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    //CriteriaBuilder로부터 CriteriaQuery객체를 얻어온다. 
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
 
    //조회의 시작점을 뜻하는 Root객체 생성 여기서 변수명 m은 JPQL에서 별칭이라고 생각하면 된다.
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    query.multiselect(m.get("username"),m.get("age")).distinct(true);
 
    //query.select(builder.array(m.get("username"),m.get("age"))).distinct(true); ->위와 같다.
 
    TypedQuery<Object[]> query2 = em.createQuery(query);
    List<Object[]> resultList = query2.getResultList();
 
    System.out.println("=================select2==================");
 
    for(Object o : resultList) {
        Object[] o2 = (Object[])o;
        System.out.print(o2[0]+" ");
        System.out.println(o2[1]);
    }
 
    System.out.println("=================select2==================");
}
cs



크게 어려울 부분은 없을 것같다. select()는 단일조회, select2()는 다중값 조회이다. 하지만 여기서 JPQL을 생각해보면 여러 값의 조회를 하나의 DTO클래스로 반환받았던 것이 기억날 것이다. Criteria 역시 NEW 명령어 쿼리를 지원한다.




NEW 명령어를 이용하여 다중값 특정 객체 매핑이후 반환 - Criteria



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
 * 반환타입을 알수 없을때, 여러개의 반환타입을 가질 경우 DTO클래스로 반환값을 갖는다.
 */
public void constructSelect() {
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    //CriteriaBuilder로부터 CriteriaQuery객체를 얻어온다. 
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    CriteriaQuery<MemberDTO> query = builder.createQuery(MemberDTO.class);
 
    //조회의 시작점을 뜻하는 Root객체 생성 여기서 변수명 m은 JPQL에서 별칭이라고 생각하면 된다.
    //반환타입을 알수 없다면 제네릭타입을 Object로 준다.
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    query.select(builder.construct(MemberDTO.class, m.get("username"),m.get("age")) );
 
    TypedQuery<MemberDTO> typeQuery = em.createQuery(query);
        
    List<MemberDTO> members = typeQuery.getResultList();
 
    System.out.println("=================constructSelect==================");
 
    for(MemberDTO member : members) {
        System.out.println(member.toString());
    }
    
    System.out.println("=================constructSelect==================");
}
cs



JPQL과 비교하여 달라진 점이 좀있다. 문자기반이 아니라 코드기반으로 쿼리를 다루다보니 DTO클래스를 풀 패키지명으로 안써도 되는 것이다. 이런것들을 생각하면 문자열을 잘못넣어서 오류날 일은 거의 없을듯 싶다. 하지만 아직까지도 문자열을 쓰는 부분은 조금 남아있다.




Java Map과 비슷한 TupleQuery - Criteria



만약 반환값이 여러개라면 위처럼 DTO로 받을 수도 있지만 마치 Map과 같은 객체처럼 느껴지는 Tuple이라는 객체를 이용해서도 다중 값 쿼리가 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
 * 튜플 - Java의 Map과 흡사한 형태로 결과를 반환받는다.
 */
public void tupleQuery() {
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    CriteriaQuery<Tuple> query = builder.createTupleQuery();
 
    //CriteriaQuery<Tuple> query = builder.createQuery(Tuple.class); -> 위와 같다.
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
    query.multiselect(m.get("username").alias("username"),m.get("age").alias("age"));
    //query.select(builder.tuple(m.get("username").alias("username"),m.get("age").alias("age")))
 
    ->위와같다.
 
    /*
     * 멤버전체를 하나의 튜플로 받을때.
     * 
     * query.select(builder.tuple(m.alias("m"))); 
     * 이러면 m이라는 별칭으로 tuple에서 회원엔티티를 얻어올 수 있다.
     */
    TypedQuery<Tuple> query2 = em.createQuery(query);
    List<Tuple> resultList = query2.getResultList();
 
    System.out.println("=================tupleQuery==================");
 
    for(Tuple t : resultList) {
        System.out.println(t.get("username",String.class)+" "+t.get("age",Integer.class));
    }
    System.out.println("=================tupleQuery==================");
}
cs



코드만 봐도 조금은 직관적으로 보인다. Tuple의 key가 될 값을 반환value.alias(key값)로 지정해준다. 그런다음 결과를 꺼낼때 Tuple객체에서 키값과 반환타입을 넘겨 꺼낸다. 위의 주석에도 있듯이 스칼라타입의 필드말고 엔티티를 전체로 튜플의 값으로도 설정해줄수 있다.

결론적으로 튜플은 이름 기반이므로 순서 기반의 Object[]로 반환받는 것보다 훨씬 안전하다. 그리고 tuple.getElements() 같은 메소드를 사용해서 현재 튜플의 별칭과 자바 타입도 조회가능하다.






조인(Join & Fetch Join) - Criteria



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
 * Join
 * 
 * public enum JoinType{
 *     INNER,LEFT,RIGHT
 * }
 */
public void join() {
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    CriteriaQuery<Tuple> query = builder.createTupleQuery();
 
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    //JPQL에서 조인구문과 연관 엔티티의 별칭이라 보면된다.
    Join<MemberJPQL, TeamJPQL> t =m.join("team",JoinType.INNER);
 
    query.multiselect(m.alias("m"),t.alias("t"))
         .where(builder.equal(t.get("name"), "티스토리1"));
 
    TypedQuery<Tuple> query2 = em.createQuery(query);
    List<Tuple> resultList = query2.getResultList();
 
    System.out.println("=================join==================");
 
    for(Tuple tuple : resultList) {
        MemberJPQL member = tuple.get("m",MemberJPQL.class);
        TeamJPQL team = tuple.get("t",TeamJPQL.class);
        System.out.println(member.toString());
        System.out.println(team.toString());
    }
 
    System.out.println("=================join==================");
}
 
public void fetchJoin() {
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class);
 
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    //fetchJoin이다. 만약 외부조인이라면 밑에 주석과 같이 left,right를 넣어준다.
    m.fetch("team"/*,JoinType.LEFT*/);
 
    query.select(m)
         .where(builder.equal(m.get("username"), "여성게1"));
    
    TypedQuery<MemberJPQL> query2 = em.createQuery(query);
    List<MemberJPQL> resultList = query2.getResultList();
 
    System.out.println("=================fetchJoin==================");
 
    for(MemberJPQL member : resultList) {
        System.out.println(member.toString());
    }
 
    System.out.println("=================fetchJoin==================");
}
cs



JPQL에서 다루었던 조인과 패치조인이다. 크게 어려울 부분은 없을 것같아 몇가지만 설명하려고 한다. 


Join<MemberJPQL, TeamJPQL> t =m.join("team",JoinType.INNER);

->JPQL과 비교하면 ~ [inner] join m.team t 와 비슷한 구문이라고 보면된다.


그리고 튜플로 반환받기위해 두개의 엔티티 모두에게 별칭을 주었고 마지막 결과를 출력하는 부분에 별칭을 이용하여 객체를 얻어왔다.



그 다음은 패치조인이다.


m.fetch("team"/*,JoinType.LEFT*/);

-> 

일반 조인과는 다르게 반환값을 받아서 사용하지 않는다. 즉, 조회의 결과로 나오는 회원엔티티의 team필드에 값이 세팅되서 나오는 것이다. 그래서 결과값을 받을 경우 회원엔티티로만 받고 그 회원엔티티의 팀엔티티 필드에서 값을 꺼내 사용한다.(사실 Criteria에서 패치조인을 이용하여 두개의 결과값을 받을 수 있는지는 확실히 모르겠다. 혹시 아시는 분은 댓글부탁드립니다.)





Parameter Binding



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public void paramBind(String param) {
 
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
 
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
        
 
    CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class);
 
        
 
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
        
 
    //fetchJoin이다. 만약 외부조인이라면 밑에 주석과 같이 left,right를 넣어준다.
 
    m.fetch("team"/*,JoinType.LEFT*/);
 
        
 
    query.select(m).where(builder.equal(m.get("username")
 
                 ,builder.parameter(String.class,"username")));
 
    TypedQuery<MemberJPQL> query2 = em.createQuery(query)
 
                    .setParameter("username", param);
 
        
 
    List<MemberJPQL> resultList = query2.getResultList();
 
        
 
    System.out.println("=================paramBind==================");
 
        
 
    for(MemberJPQL member : resultList) {
 
        System.out.println(member.toString());
 
    }
 
        
 
    System.out.println("=================paramBind==================");
 
}
cs


query.select(m).where(builder.equal(m.get("username")

 ,builder.parameter(String.class,"username")));

-> 이름기반 파라미터 정의를 해준다. (Predicate 객체로 선언해도되지만 이렇게 where절에 바로 넣어도된다.)




TypedQuery<MemberJPQL> query2 = em.createQuery(query)

.setParameter("username"param);

->JPQL과 동일하게 setParameter메소드를 호출해서 세팅해준다.



Criteria Meta Code Generate



지금까지 간단하게 Criteria 쿼리를 설명했다. 확실히 그냥 JPQL을 사용할때 보다 문자열을 사용하여 쿼리를 작성하는 부분이 확 줄었다. 그렇지만 그렇다고 100% 문자열을 사용하지 않는 것은 아니다. 그 말은 100% 실수하지 않으리라는 보장이 없는 것이다. 이때 사용할 수 있는 것이 Meta Code를 생성하여서 위에서 조금 사용한 문자열마저 사용하지 않는 것이다. Meta Code를 생성하면 각 엔티티 클래스마다 메타정보를 가진 자바파일들이 생성되게 된다.



<pom.xml>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- Criteria Meta Code -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jpamodelgen</artifactId>
    <version>1.3.0.Final</version>
</dependency>
 
<!-- Criteria Meta Code Plugin -->
 
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-compiler-plugin</artifactId>
       <configuration>                    
        <source>1.8</source>
        <target>1.8</target>
        <outputDirectory>target/generated-sources/annotations</outputDirectory>
        <compilerArguments>
            <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
         </compilerArguments>
    </configuration>
</plugin>
cs



이렇게 두개를 설정해준다. 첫번째 의존 라이브러리를 내려받아주고, 두번째는 Meta Code를 생성하기 위한 플러그인 설정을 해주는 것이다.





->Run As..->Run Configuration 에 들어가서 해당 프로젝트의 메이븐 빌드 Goals를 입력하고 Run을 해준다.




->빌드가 완료되었다면 target->generated-sources에 Meta Code들이 생성되어 있을 것이다. 이 다음은 과정은 각자의 IDE환경에 따라 다르다. 만약 target>generated-sources가 자동으로 클래스패스로 잡힌다면 문제가 없지만 만약 클래스패스로 잡히지 않는다면 직접 등록해주어야한다. 클래스패스가 디폴트로 잡히지 않는다는 가정하에 진행한다.








-> 첫 이미지처럼 반드시 패키지명직전까지를 패스로 잡아야한다. 그래서 Meta Code생성시 package관련 에러가 뜨지 않는다. 마지막으로 Apply를 누르면 방금생성된 메타코드가 클래스패스에 들어가있을 것이다(기존의 target부분에는 코드가 없어짐). 혹시나 에러표시가 프로젝트에 계속 난다면 maven update를 하길바란다.






Criteria Meta Code 적용후 쿼리




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
 * Criteria Meta Code 생성
 */
public void afterMetaCodeGen(String username,String teamname) {
    //EntityManager 혹은 EntityManagerFactory에서 CriteriaBuilder를 가져온다.
    CriteriaBuilder builder = em.getCriteriaBuilder();
 
    CriteriaQuery<MemberJPQL> query = builder.createQuery(MemberJPQL.class);
    Root<MemberJPQL> m = query.from(MemberJPQL.class);
 
    //fetchJoin이다. 만약 외부조인이라면 밑에 주석과 같이 left,right를 넣어준다.
    m.fetch("team"/*,JoinType.LEFT*/);
 
    List<Predicate> searchConditions = new ArrayList<Predicate>();
 
    if(!StringUtils.isEmpty(username)){
        searchConditions.add(builder.equal(m.get(MemberJPQL_.username)
                            ,builder.parameter(String.class,"username")));
    }
 
    if(!StringUtils.isEmpty(teamname)) {
        searchConditions.add(builder.equal(m.get(MemberJPQL_.team).get(TeamJPQL_.name)
                ,builder.parameter(String.class,"teamname")));
    }
 
    query.select(m)
         .where(builder.and(searchConditions.toArray(new Predicate[0])));
 
    TypedQuery<MemberJPQL> query2 = em.createQuery(query);
 
    if(!StringUtils.isEmpty(username)){
        query2.setParameter("username", username);
    }
 
    if(!StringUtils.isEmpty(teamname)) {
        query2.setParameter("teamname", teamname);
    }
 
    List<MemberJPQL> resultList = query2.getResultList();
 
    System.out.println("=================afterMetaCodeGen==================");
 
    for(MemberJPQL member : resultList) {
        System.out.println(member.toString());
    }
 
    System.out.println("=================afterMetaCodeGen==================");
 
}
cs




m.get(MemberJPQL_.username)

->여기에서 사용하던 문자열이 없어졌다. 이제 조금은 에러가 줄것 같다.






지금까지 간단하게 Criteria를 다뤄봤습니다. 사실 개인적인 생각으로는 이것이 편해진것인가...? 하는 생각이 조금 들었습니다. 문자기반으로 작성하지 않을 뿐이고 오히려 코드는 그냥 JPQL을 쓸때보다 복잡해진것같기도 하는 생각이 듭니다.(개인적인 생각입니다. 물론 런타임시점에 에러를 발견해야하는 큰 문제를 해결해준 것만으로도 엄청난 가치가 있다고 생각듭니다.) 그래서 다음 포스팅에서는 쉽고, 간단하고, 코드로 쿼리를 다룰 수 있는 QueryDSL을 이용하여 JPA쿼리를 다뤄보겠습니다.



posted by 여성게
:
Web/JPA 2019. 2. 9. 13:44

JPA - JPQL(객체지향쿼리)


jpa-study.zip

(예제소스파일/ jpql package참조)

JPQL은 가장 중요한 객체지향 쿼리 언어이다. Criteria나 QueryDSL은 결국 JPQL을 편리하게 사용하도록 도와주는 기술이므로 JPA로 데이터베이스 엑세스를 다룬다면 JPQL은 꼭 필수라고 생각이 든다. SQL과 꼭 닮은 쿼리 언어이며 SQL은 데이터 중심의 쿼리라고 하면 JPQL은 엔티티를 대상으로 하는 쿼리 언어라고 할 수 있다. 결국 JPA에서 해당 JPQL을 분석한 다음 적절한 SQL로 변환해주어서 데이터베이스에서 데이터를 가져오는 것이다.

JPQL 특징

1. 엔티티 객체를 조회하는 객체지향 쿼리이다.(테이블 대상이 아니다.)
2. JPQL은 SQL을 추상화해서 특정 데이터베이스에 의존하지 않는다.(데이터베이스 방언(Dialect)만 변경하면 소스 수정없이 데이터베이스 변경가능)
3. SQL보다 간결하다.






SELECT문


 


간단한 Select문이다. 일단 from절은 절대 테이블명이 아닌 엔티티명을 넣어주어야한다.(@Entity에서 name을 정의하지 않았다면 테이블명) 그리고 where절 혹은 프로젝션(select 다음 조회할 목록을 지칭) 쪽에서 엔티티의 필드를 참조하기 위해서는 반드시 클래스 내부에 정의된 필드 네임을 그대로 가야한다. 또한 from절 다음 엔티티의 별칭을 꼭 필수로 줘야한다. jqpl을 작성한 이후 쿼리를 생성할때는 2개의 객체를 반환 받을 수 있다. TypeQuery와 Query 클래스이다. 두개의 차이점은 주석에 명시되어있다. 



TypeQuery, Query


위에서 설명한 것과 같이 반환타입이 명확한가? 명확하지 않은가? 에 따라 두개중 하나의 쿼리 객체를 이용한다.




파라미터 바인딩(parameter bind)


1. 이름 기준 파라미터 - 이름 기준으로 파라미터를 구분하는 방법 ":"를 사용한다.

2. 위치 기준 파라미터 - 위치 기준으로 파라미터를 구분하는 방법 "?"를 사용한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
 * SELECT문
 * 1. 엔티티와 속성은 대소문자를 구분한다. but JPQL keyword는 대소구분을 않는다.
 * 2. Member는 테이블명이 아닌, 클래스명도 아닌 엔티티 명이다.
 * 3. 엔티티명에 별칭은 필수값으로 사용해야한다.
 */
public void select(){
        String jpql="select m from MemberJPQL m where m.username = '여성게'";
        /*
         * TypeQuery - 반환타입이 명확할때
         * Query - 반환타입이 명확하지 않을때
         */
        TypedQuery<MemberJPQL> query=em.createQuery(jpql,MemberJPQL.class);
 
        /*
         * getResultList() - 결과를 리스트로 받는다.(결과가 없으면 빈 컬렉션을 반환.)
         * 
         * getSingleResult() - 결과가 정확히 하나임을 기대할때 사용한다.
         *                      - 결과가 없으면 javax.persistence.NoResultException
         *                      - 결과가 하나 이상이면
                javax.persistence.NonUniqueResultException
         */
        List<MemberJPQL> resultList=query.getResultList();
 
        System.out.println("================select=================");
 
        for(MemberJPQL m:resultList){
            System.out.println(m.toString());
        }
 
        String jpql2="select m.username,m.age from MemberJPQL m where m.username = '여성게'";
        Query query2=em.createQuery(jpql2);
        List resultList2=query2.getResultList();
 
        for(Object o:resultList2){
            Object[]result=(Object[])o;
            for(Object o2:result){
                System.out.println("result element => "+o2);
            }
        }
        System.out.println("================select=================");
}
cs



위치기준 파라미터 바인딩보다 이름기준으로 바인딩하는 것이 더 명확함으로 이름기준 파라미터 바인딩 사용을 하는 것이 나을 듯 싶다.

프로젝션(projection)


SELECT 절에 조회할 대상을 지정하는 것을 프로젝션이라 한다. [SELECT {프로젝션 대상} FROM~] 프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다. 스칼라 타입은 숫자,문자 등 기본 데이터 타입을 뜻한다(엔티티 클래스에서 int,Long,String 등의 필드)

여기서 하나 얘기할 것이 있다면 임베디드 타입 프로젝션은 절대 조회의 시작점이 될 수 없다. 즉, From절에서 엔티티를 정의하고 select에서 엔티티.임베디드필드로 참조해야한다.(SELECT m.embeddedTypeField FROM MemberEntity m ~)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
 * projection - SELECT 절에 조회할 대상을 지정하는 것.
 *
 * ex) SELECT {projection} FROM ~
 *
 * 1.엔티티
 * 2.임베디드
 * 3.스칼라(숫자,문자 등 기본 데이터타입)
 */
public void projection() {
        /*
         * 엔티티타입 프로젝션 - 조회한 엔티티는 영속성컨텍스트에서 관리된다.
         */
        String jpql = "select m.team from MemberJPQL m where m.username = '여성게1'";
        TypedQuery<TeamJPQL> query = em.createQuery(jpql,TeamJPQL.class);
        List<TeamJPQL> resultList = query.getResultList();
 
        System.out.println("================projection=================");
 
        for(TeamJPQL t : resultList) {
            System.out.println(t.toString());
        }
 
        /*
         * 임베디드타입 프로젝션 - 임베디드 타입은 조회의 시작점이 될 수 없기에 Order엔티티로 조회를 시작해
         *                      그다음 임베디드 타입을 참조한다.
         */
        String jpql2 = "select o.address from OrderJPQL o where o.product.name ='맥북1'";
        TypedQuery<AddressJPQL> query2 = em.createQuery(jpql2,AddressJPQL.class);
        List<AddressJPQL> resultList2 = query2.getResultList();
 
        for(AddressJPQL o : resultList2) {
            System.out.println(o.toString());
        }
 
        /*
         * 스칼라타입 - 여러종류의 스칼라타입을 받기 위해서는 TypeQuery를 사용할 수 없다.
         * 해결책 - new 명령어를 사용한 DTO 타입변환
         * 반드시 밑 문자열에 들어간 파라미터의 순서대로 해당 dto에 생성자가 존재해야한다.
         */
        String jpql3 = "select new com.spring.jpa.jpql.MemberDTO(m.username,m.age)"
        + " from MemberJPQL m where m.username = '여성게1'";
        TypedQuery<MemberDTO> query3 = em.createQuery(jpql3,MemberDTO.class);
        List<MemberDTO> members = query3.getResultList();
 
        for(MemberDTO dto : members) {
            System.out.println(dto.toString());
        }
 
        System.out.println("================projection=================");
}
cs



위에서 조금 특이한 것이 있다면 jpql3이다. 조회할 대상이 여러개이면 TypeQuery로는 조회할 수 없기 때문에 하나의 DTO를 만들어서 위와 같이 "new" 명령어를 사용해서 결과를 DTO로 받아 TypeQuery 객체를 이용할 수 있다.



페이징 API


페이징을 하기 위해서는 다소 지루하고 반복적인 일이다. 게다가 데이터베이스 구현체에 따라 문법도 다르다. 하지만 JPA는 모든 데이터베이스에서 동일하게 사용할 수 있는 페이징 메소드를 제공해준다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void paging() {
        String jpql = "select m from MemberJPQL m ";
        TypedQuery<MemberJPQL> query = em.createQuery(jpql,MemberJPQL.class);
        //11부터 조회
        query.setFirstResult(10);
        //20개 11~30까지 조회
        query.setMaxResults(20);
        List<MemberJPQL> members = query.getResultList();
 
        System.out.println("================paging=================");
 
        for(MemberJPQL m : members) {
            System.out.println(m.toString());
        }
 
        System.out.println("================paging=================");
}
 
cs



현 예제는 값을 직접 입력해주었지만, 추후에는 컨트롤러 단에서 페이징에 대한 데이터를 직접 주입받아서 사용하지 않을까 싶다.(Pagable 클래스)






집합과 정렬



함수 

설명 

COUNT 

결과 수를 구한다. 반환타입:Long 

MAX,MIN 

최대,최소 값을 구한다. 

 AVG

평균값을 구한다. 반환타입: Double 

 SUM

합을 구한다.

정수합 반환타입 : Long

소수합 반환타입 : Double

BigInteger,BigDecimal합 반환타입 : BigInteger,BigDecimal  



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void groupFunction() {
        String jpql = "select count(m) from MemberJPQL m ";
        Query query = em.createQuery(jpql);
        Long count = (Long) query.getSingleResult();
 
        System.out.println("================groupFunction=================");
        System.out.println("count = "+count);
        System.out.println("================groupFunction=================");
}
 
public void groupByHaving() {
        String jpql = "select t.name, count(m.age) "
                    + "from MemberJPQL m left join m.team t "
                    + "group by t.name ";
 
        Query query = em.createQuery(jpql);
        List members = query.getResultList();
 
        System.out.println("================groupByHaving=================");
 
        for(Object o : members) {
            Object[] result = (Object[]) o;
            for(Object o2 : result) {
                System.out.print("result element => "+o2);
            }
            System.out.println();
        }
        System.out.println("================groupByHaving=================");
}
 
cs



집합함수 사용시 참고사항


1.Null 값은 무시하므로 통계에 잡히지 않는다.

2.만약 값이 없는데 집합함수를 사용한다면 Null값이 된다.(단 count는 0)

3.DISTINCT를 집합함수 안에 사용하여 중복된 값을 제거하고 집합을 구할 수 있다.

  (ex select count(distinct m.age) from MemberJPQL m)

4.DISTINCT를 count에서 사용할 때 임베디드타입 필드는 지원하지 않는다.




여기까지 JPQL포스팅을 한번 끊고 양이 많아 JPQL 조인부터는 다음 포스팅에 정리하겠습니다. :)







posted by 여성게
:
Web/JPA 2018. 10. 14. 19:24

JPA는 쿼리가 너무 정적이라는 단점이 존재합니다. 이러한 단점을 극복하기 위하여 JAP+QueryDSL을 통해 type-safe하고, 자바의 메소드를 통한 동적인(유연한) 쿼리 작성이 가능해집니다.


이전 글에서 JPA사용법에 대하여 간단히 작성했지만 이번 글에서도 JPA부터 설명하겠습니다.



pom.xml 설정


JPA 및 querydsl 등 각종 dependency 설정입니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
<!--querydsl version 명시-->
        <querydsl.version>4.1.4</querydsl.version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
<!--querydsl dependency-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.0</version>
        </dependency>
<!--JPA dependency-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> 
            <version>${org.springframework-version}</version> </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
<!--maven plugin 추가-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <!-- 사용할 자바버전 -->
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
<!--Q클래스들이 생성될 위치입니다.-->
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 

cs




Entity class 작성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


maven plugin에서 JpaAnnotationProccesor가 @Entity를 보고 관련 Q클래스를 생성해줍니다.



maven build




run 해주면 target>generate-sources/java에 해당 Q클래스들이 생성됩니다. 만약 생성이 되었지만 Q클래스들을 인식하지 못하였을 경우에는 build path에 해당 폴더를 add folder해줍니다. 



Repository 작성



1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import java.util.List;
 
import com.spring.jpa.model.UserEntity;
 
public interface JpaUserRepositoryCustom {
    List<UserEntity> findAllLike(String keyword);
}
 
cs


커스텀레포지터리를 작성해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.spring.jpa.repository;
 
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
 
import com.querydsl.jpa.JPQLQuery;
import com.spring.jpa.model.QUserEntity;
import com.spring.jpa.model.UserEntity;
 
@Repository
public class JpaUserRepositoryImpl extends QuerydslRepositorySupport implements JpaUserRepositoryCustom{
 
    public JpaUserRepositoryImpl() {
        super(UserEntity.class);
        // TODO Auto-generated constructor stub
    }
 
    @Override
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
 
    @Override
    public List<UserEntity> findAllLike(String keyword) {
        // TODO Auto-generated method stub
        QUserEntity qUserEntity=QUserEntity.userEntity;
        JPQLQuery<UserEntity> query=from(qUserEntity);
        List<UserEntity> userEntityList=query.where(qUserEntity.userName.like(keyword)).fetch();
        return userEntityList;
    }
 
}
 
cs


커스텀레포지터리 구현체를 만들어줍니다. 주의해야할점은 생성자에서 super(domain.class)입니다. 도메인클래스 인자는 해당 구현체가 필요한 것이 아니라, QuerydslRepositorySupport이 필요한 인자입니다. 꼭 넣어주어야합니다 !!!!




그리고 커스텀인터페이스에서 선언한 메소드를 오버라이드하여, querydsl을 이용한 type-safe한 동적인 쿼리를 작성해줍니다. 여기서 querydsl은 일반 Jpa 레포지토리의 기능을 업그레이드 해주는 역할이라고 보시면 됩니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long>,JpaUserRepositoryCustom{
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


일반 Jpa 레포지토리에 커스텀인터페이스 상속을 해줍니다. 이렇게 커스텀인터페이스를 상속해주면 커스텀인터페이스 구현체에서 구현한 querydsl 메소드를 일반 jpa레포지토리에서 사용이 가능해집니다. 즉, 일반 jpa레포지터리의 기능을 그대로 이용하면서 업그레이드된 querydsl쿼리를 같이 이용할 수 있게 되는 것입니다.



Controller 작성


대충짰습니다....controller에서 repository를 다이렉트로 주입하고... 따라하시지 마시고 테스트용도이니 만약 사용하실때는 꼭 controller->service->repository 구조로 이용해주세요 !!!!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.spring.jpa.controller;
 
import java.util.Arrays;
import java.util.List;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.repository.JpaUserRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    @Autowired
    JpaUserRepository jpaUserRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) throws JsonProcessingException {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        
        XmlMapper xmlMapper=new XmlMapper();
        String xmlString=xmlMapper.writeValueAsString(user);
        
        log.info(xmlString);
        
        List<UserEntity> userList=jpaUserRepository.findAllLike("여성게");
        log.info("userEntity list={}",userList.get(0).getUserName());
        
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs



확실히.. 편하긴 하지만 여기까지 오기에 복잡한 설정은 아니지만 귀찮은 설정들이 많았내요... 조금 적응되면 이 기술도 자유롭게 사용가능하겠죠? mybatis를 선호하였던 저였지만, jpa+querydsl을 잠시나마 사용해보니 확실히 기술을 익히고 사용한다면 mybatis보다 훨씬 유연한 데이터베이스 관련 기술 일것 같습니다.



혹시 여기까지 오시다가 cannot change web module version 2.5 to ....어쩌고 맞나?.. 이런 에러가 뜬다면 자기가 사용중인 web module 버전과 maven에 명시되어 있는 버전과 비교를 하시고 꼭 maven jdk 버전이랑도 같이 맞춰주시면 해당 에러는 사라집니다... 설정을 다 다시 마추고 maven update하면 에러는 싹 사라집니다. 

posted by 여성게
: