Web/JPA 2019. 2. 11. 01:07

JPA - QueryDSL, JPQL Query Builder Class



이전 포스팅에서 다뤘던 JPA Criteria는 문자가 아닌 코드로 JPQL을 작성하도록 도와주는 쿼리빌더였기 때문에 문법 오류를 컴파일 단계에서 잡을 수 있고 IDE툴을 이용하여 자동완성 기능의 도움을 받을 수 있기 때문에 여러가지 장점이 있었다. 하지만 Criteria의 가장 큰 단점은 코드양이 줄지도 않고 너무 복잡하며 바로바로 쿼리를 예상하기가 쉽지 않았다. 하지만 이번에 다룰 QueryDSL은 Criteria와 같이 문자가 아닌 코드로 쿼리를 작성하며, 쉽고 간결하고 빌더로 작성된 모양도 쿼리와 비슷해 쉽게 예상이 가능하다. 오히려 Criteria보다 사용하기 편하다. 하지만 큰 문제라고는 할 수 없지만 Criteria는 JPA가 지원하는 표준이고, QueryDSL은 표준은 아니고 그냥 오픈소스이다. 


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

▶︎▶︎▶︎QueryDSL Reference 번역 By 최범균

▶︎▶︎▶︎JPQL(객체지향쿼리),Java Persistence Query Language

QueryDSL이 지원하는 프로젝트

-QueryDSL은 처음에 HQL(Hibernate Query Language)을 코드로 작성할 수 있도록 해주는 프로젝트로 시작해서 지금은 JPA, JDO, JDBC, Lucene, Hibernate Search, 몽고DB, 자바 컬렉션 등을 다양하게 지원하고 있는 오픈소스이다. 이름 그대로 데이터를 조회하는 데에 기능이 아주 특화되어있다.




QueryDSL 설정


필요라이브러리

1
2
3
4
5
6
7
8
9
10
11
12
<!-- QueryDSL Library -->
<dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>3.6.3</version>
</dependency>
<dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>3.6.3</version>
        <scope>provided</scope>
</dependency>
cs


환경설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- QueryDSL 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>
                      <outputDirectory>target/generated-sources/java</outputDirectory>
                      <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                </configuration>
        </execution>
   </executions>
</plugin>
cs







혹시 프로젝트에 에러가 있다면 target/generated-sources/java가 클래스패스에 등록되어있는지 확인해보면 될듯싶다. 대게 IDE가 자동으로 해주지만 안되는 경우도 있지 않을까 생각한다.




QueryDSL에서 사용되는 객체설명 - JPAQuery, Q클래스


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
public void intro() {
        
        //JPAQuery 객체를 생성한다. 생성자 매개변수로 EntityManager인스턴스 전달
        JPAQuery query = new JPAQuery(em);
        
        //조회의 시작점인 엔티티를 Q_.class로 생성해준다. 생성자로 별칭을 넘겨준다.
        QMemberJPQL member = new QMemberJPQL("m");
        
        /*만약 같은 엔티티를 조인하거나 같은 엔티티를 서브쿼리에 사용하는 것이 아니라면
        QMemberJPQL member = QMemberJPQL.memberJPQL 로 생성해도 위와 동일하다.
        만약 위와같이 기본 인스턴스를 사용한다면 클래스 package밑에 
        import static ~.QMemberJPQL.memberJPQL로 임포트해놓고 사용하면 더 간결해진다.*/
        
        List<MemberJPQL> members = query.from(member)
                                        .where(member.username.eq("여성게1"))
                                        .orderBy(member.age.desc())
                                        .list(member); //매개변수에 조회할 프로젝션을 작성한다.
        
        System.out.println("=====================intro======================");
        
        for(MemberJPQL m : members) {
                System.out.println(m.toString());
        }
        
        System.out.println("=====================intro======================");
}
cs


JPAQuery는 빌더패턴으로 된 클래스로써 쿼리 메소드들을 계속 해서 호출하고 있다. 결국에 이 객체가 나중에 JPQL 쿼리로 변환이 되는 것이다. 위의 소스로 간단히 JPQL과 비교를 해보면,


 JPQL

QueryDSL 

 select m from MemberJPQL m .... 

 QMemberJPQL member = new QMemberJPQL("m");

 select m ....

.list(member) 

 from MemberJPQL m

.from(member) 

 where m.username = ?1 

.where(member.username.eq("여성게1") 

 order by m.age desc 

.orderBy(member.age.desc()) 


표시에서도 보이듯이 자바코드로 변환하였지만 JPQL과 비교하여 이질감이 없다. 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
/*
 * 검색조건 쿼리
 */
public void searchCondition() {
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        //and,or을 사용할 수 있다. 또한 ","구분자로 나열해도 된다(and)
        /*
         * between(A,B) - 사이값
         * contains(A) - SQL에서 like '%A%'와 같다.
         * startWith(A) - SQL에서 like 'A%'와 같다.
         * 
         * 이렇게 다양한 메소드를 제공해준다.
         */
        List<MemberJPQL> members = query.from(qMember)
                                    .where(qMember.username.contains("여성게").and(qMember.age.gt(36)))
                                    .list(qMember);
        
        System.out.println("=====================searchCondition======================");
        
        for(MemberJPQL m : members) {
                System.out.println(m.toString());
        }
        
        System.out.println("=====================searchCondition======================");
}
cs


QueryDSL의 where 절에는 and 나 or을 사용할 수 있다. 그리고 쿼리 타입의 필드는 비교연산을 위한 대부분의 메소드를 명시적으로 제공한다. 즉, IDE가 제공하는 자동완성 기능을 도움받아 문법오류없는 쿼리 작성이 쉽게 가능하다!




결과 조회 - uniqueResult(), singleResult(), list()



.uniqueResult() - 조회 결과가 한 건일 때 사용한다. 조회 결과가 없으면 null을 반환하고 결과가 하나 이상이면 

  com.mysema.query.NonUniqueResultException 예외가 발생한다.

  

.singleResult() - uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터를 반환한다.

 

.list() - 결과가 하나 이상일 때 사용한다. 결과가 없으면 빈 컬렉션을 반환한다.



페이징과 정렬



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
/*
 * 페이징과 정렬
 */
public void pagingAndOrderBy() {
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        /*List<MemberJPQL> members = query.from(qMember)
                                          .where(qMember.age.gt(30))
                                          .orderBy(qMember.age.desc())
                                          .offset(0).limit(2)
                                          .list(qMember);*/
        
        /*      
         * 위와 동일한 기능이다.
         * QueryModifiers queryModifiers = new QueryModifiers(2L,0L); limit,offset      
         * List<MemberJPQL> members = query.from(qMember)
                                        .where(qMember.age.gt(30))
                                        .orderBy(qMember.age.desc())
                                        .restrict(queryModifiers)
                                        .list(qMember);
        */
        
        /* 하지만 실제 페이징 처리를 하려면 검색된 전체 데이터수를 알아야한다. 이때는 list() 대신 listResults()를 사용한다.
          listResults()를 사용하면 전체 데이터 조회를 위한 count 쿼리를 한번더 실행한다.*/
          
          SearchResults<MemberJPQL> result = query.from(qMember)
                                                  .where(qMember.age.gt(30))
                                                  .orderBy(qMember.age.desc())
                                                  .offset(0).limit(2)
                                                  .listResults(qMember);
                                                                        
          long totalNum = result.getTotal(); //검색된 전체데이터수
          long limitNum = result.getLimit();
          long offsetNum = result.getOffset();
 
          //조회된 데이터(전체 데이터가 아닌 위의 리밋과 오프셋을 이용하여 조회한 데이터)
          List<MemberJPQL> members = result.getResults(); 
        
        
        System.out.println("=====================pagingAndOrderBy======================");
        System.out.println("전체 데이터수:"+totalNum +",한 페이지당 글수:"+limitNum +",시작글 번호:"+offsetNum);
        for(MemberJPQL m : members) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================pagingAndOrderBy======================");
        
}
cs


코드만 봐도 직관적으로 무슨 코드인지 알 수 있다.



집합



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void groupAndHaving() {
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        List<MemberJPQL> members = query.from(qMember)
                                        .groupBy(qMember.username)
                                        .having(qMember.age.gt(36))
                                        .list(qMember);
        
        System.out.println("=====================groupAndHaving======================");
        for(MemberJPQL m : members) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================groupAndHaving======================");
}
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
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
public void standardJoin() {
        
        JPAQuery query = new JPAQuery(em);
        
        QOrderJPQL qOrder = QOrderJPQL.orderJPQL;
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QProductJPQL qProduct = QProductJPQL.productJPQL;
        
        List<OrderJPQL> orders = query.from(qOrder)
                                      .where(qOrder.member.username.eq("여성게1"))
                                      .join(qOrder.member,qMember)
                                      .leftJoin(qOrder.product,qProduct)
                                      .list(qOrder);
        
        System.out.println("=====================standardJoin======================");
        for(OrderJPQL m : orders) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================standardJoin======================");
}
 
public void joinOn() {
        JPAQuery query = new JPAQuery(em);
        QOrderJPQL qOrder = QOrderJPQL.orderJPQL;
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QProductJPQL qProduct = QProductJPQL.productJPQL;
        
        List<OrderJPQL> orders = query.from(qOrder)
                          .leftJoin(qOrder.member,qMember)
                          .on(qOrder.product.name.eq("맥북1"))
                          .list(qOrder);
 
        System.out.println("=====================joinOn======================");
        for(OrderJPQL m : orders) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================joinOn======================");
        
}
 
public void fetchJoin() {
        JPAQuery query = new JPAQuery(em);
        QOrderJPQL qOrder = QOrderJPQL.orderJPQL;
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QProductJPQL qProduct = QProductJPQL.productJPQL;
        
        /*
         * join on 절에는 패치조인 불가
         */
        List<OrderJPQL> orders = query.from(qOrder)
                          .leftJoin(qOrder.member,qMember).fetch()
                          .list(qOrder);
 
        System.out.println("=====================fetchJoin======================");
        for(OrderJPQL m : orders) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================fetchJoin======================");
}
 
public void thetaJoin() {
        JPAQuery query = new JPAQuery(em);
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QOrderJPQL qOrder = QOrderJPQL.orderJPQL;
        List<OrderJPQL> orders = query.from(qMember,qOrder)
                                      .where(qOrder.member.eq(qMember))
                                      .list(qOrder);
 
        System.out.println("=====================thetaJoin======================");
        for(OrderJPQL m : orders) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================thetaJoin======================");
}
cs


조인은 내부조인,외부조인 등을 사용할 수 있고 join on과 성능 최적화를 위한 fetch 조인도 사용할 수 있다. QueryDSL 조인의 기본 문법은 우선 조인에 사용될 쿼리타입 클래스들을 모두 생성해준다. 그리고 join메소드 파라미터 첫번째에는 조인 대상(Order의 연관필드), 두번째 파라미터에 별칭으로 사용할 쿼리타입(Q클래스)을 지정하면 된다.



서브쿼리 - JPASubQuery 객체



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
/*
 * 서브쿼리
 */
public void subQuery() {
        
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QMemberJPQL subMember = new QMemberJPQL("subMember");
        
        List<MemberJPQL> results = query.from(qMember)
                                        .where(qMember.age.eq(
                                        new JPASubQuery()
                                                .from(subMember)
                                                .unique(subMember.age.max())
                                        )).list(qMember);
        
        System.out.println("=====================subQuery======================");
        for(MemberJPQL m : results) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================subQuery======================");
}
 
/*
 * 서브쿼리
 */
public void subQuery2() {
        
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        QMemberJPQL subMember = new QMemberJPQL("subMember");
        
        List<MemberJPQL> results = query.from(qMember)
                                        .where(qMember.in(
                                        new JPASubQuery()
                                                .from(subMember)
                                                .where(subMember.username.in("여성게1","여성게3"))
                                                .list(subMember)
                                        )).list(qMember);
        
        System.out.println("=====================subQuery2======================");
        for(MemberJPQL m : results) {
        System.out.println(m.toString());
        }
        
        System.out.println("=====================subQuery2======================");
}
cs


서브쿼리는 JPASubQuery 객체를 생성하여 사용한다. 만약 서브 쿼리의 결과가 아나면 unique(),여러건이면 list()를 사용할 수 있다.



프로젝션과 결과 반환


반환 결과가 여러건 일경우 2가지의 방법이 있다. 첫번째는 튜플을 이용하는 방법이고 두번째는 DTO클래스를 이용하여 반환받는 방법이다.


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
/* 
 * 반환결과가 여러건일 경우(튜플타입사용)
 */
public void tupleType() {
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        com.mysema.query.Tuple tuple = query.from(qMember)
                                            .where(qMember.username.eq("여성게1"))
                                            .singleResult(qMember.username,qMember.age); 
                                                        //프로젝션은 여기다가 작성.
        
        System.out.println("=====================tupleType======================");
        
        System.out.println(tuple.get(qMember.username)+" "+tuple.get(qMember.age));
        
        System.out.println("=====================tupleType======================");
}
 
/*
 * 반환결과가 여러건일 경우(JPQL의 NEW명령어와 같이 DTO사용)
 * 
 * 1.Projections.bean() -> setter이용
 * 2.Projections.fields() -> 필드직접 접근.(reflection으로 접근하므로 private이여도 접근가능)
 * 3.Projections.constructor() -> 생성자로 접근, 생성자 매개변수와 순서가 동일해야함.
 */
public void nonTuple() {
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        //1
        MemberDTO dto = query.from(qMember)
                         .where(qMember.username.eq("여성게1"))
                         .singleResult
                         (Projections.bean(MemberDTO.class, qMember.username, qMember.age.as("userage")));
                                //프로젝션은 여기다가 작성. 만약 dto와 엔티티의 필드명이 다르다면 "as"메서드이용.
        /*
         * 2.~Projections.fields(MemberDTO.class, qMember.username, qMember.age.as("userage")));
         * 3.~Projections.constructor(MemberDTO.class, qMember.username, qMember.age)); 
                ->해당 타입의 매개변수를 들어가는 것뿐이니까, 필드명을 맞춰줄 필요 없음
         */
        System.out.println("=====================nonTuple======================");
        
        System.out.println(dto.toString());
        
        System.out.println("=====================nonTuple======================");
}
cs



DISTINCT



query.distinct().from(.....)으로 작성하면 된다.




수정, 삭제 배치 쿼리 - JPAUpdateClause, JPADeleteClause



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * 수정,삭제 배치 쿼리
 */
public void batchQuery() {
        JPAQuery query = new JPAQuery(em);
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        tx.begin();
        JPAUpdateClause updateBatch = new JPAUpdateClause(em, qMember);
        long count = updateBatch.where(qMember.username.eq("여성게1")).set(qMember.username,"바보멍청이").execute();
        
        /*JPADeleteClause deleteBatch = new JPADeleteClause(em, qMember);
        long count = deleteBatch.where(qMember.username.eq("여성게1")).execute();*/
        
        tx.commit();
}
cs


트랜잭션 선언은 필수이다.



동적쿼리 - BooleanBuilder



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
 * 동적쿼리
 */
public void dynamicQuery(String name,String teamName) {
        
        JPAQuery query = new JPAQuery(em);
        
        QMemberJPQL qMember = QMemberJPQL.memberJPQL;
        
        BooleanBuilder builder = new BooleanBuilder();
        if(!StringUtils.isEmpty(name) && !StringUtils.isEmpty(teamName)) {
                builder.and(qMember.username.eq(name));
                builder.and(qMember.team.name.eq(teamName));
        }
        
        MemberJPQL member = query.from(qMember).join(qMember.team).where(builder).uniqueResult(qMember);
        
        if(!ObjectUtils.isEmpty(member)) {
                System.out.println("=====================dynamicQuery======================");
                System.out.println(member.toString());
                System.out.println("=====================dynamicQuery======================");
        }
}
cs


BooleanBuilder를 이용하여 가변적인 파라미터에 따른 동적쿼리를 작성하였습니다.





사실 뭐가 편하고 뭐가 좋다라는 것은 개인적인 견해가 많이 들어가는 것도 있습니다. 그래서 저는 개인적으로 Criteria보다는 QueryDSL이 편하다고한 것이구요. 만약 복잡한 것을 더 편하게 생각하시는 분들고 계시기 때문에 제가 포스팅한 Criteria와 QueryDSL을 참고해보시고 더 편하신 것을 사용하는 것이 좋을것 같습니다.


posted by 여성게
:
일상&기타/Apple 2019. 2. 9. 23:36


Apple - iPad Pro 3세대 11인치 구매후기!(아이패드 프로 3세대 11인치)

             + 애플펜슬 2세대 , 스마트 키보드 폴리오


요즘 들어 기획서쓰랴, 기술검토회의하랴 정말 회의가 많아졌습니다. 팀 내부적으로도 회의가 많다보니 계속해서 맥북을 들고 회의하기가 힘들어졌습니다. 그래서 이번에 새로 나온 아이패드 프로 3세대 구매를 결심했습니다. 애플제품을 쓰는 무엇보다도 중요한 이유중 하나는 iCloud입니다. 아이패드에서 메모를 하면 내 맥북과 아이폰에도 메모가 동기화가 되서 볼수 있어서 회의 이후 맥북으로 회의록을 작성할 수도 있습니다. 이런저런 이유로 일단 아이패드를 구입하게 된것입니다.



아이패드 프로 3세대 외형

▶︎▶︎▶︎애플 공식홈페이지



무엇보다 이번 아이패드 프로의 큰 변화는 아이폰 X 시리즈와 같이 홈버튼이 사라졌다는 점입니다. 그렇기에 더 작은 사이즈에 더 큰 디스플레이를 누릴수 있게 되었습니다. 그리고 베젤도 확 줄어들고 많이 얇아진 느낌이 듭니다. 디자인적으로는 만족할 만큼 깔끔하고 이쁩니다.






이제는 맥북과 함께 충전기를 사용할 수 있게 되었습니다. 이번 아이패드 프로부터는 USB-C으로 변경되었기 때문입니다. USB-C-HDMI를 연결하여 영상 출력이 가능하고 SD카드나 유선랜 이용도 가능하게 되었습니다.






이제는 손가락도 필요없게 되었습니다. 아이폰 X 시리즈와 동일하게 FACE ID가 도입되었기 때문입니다. 그리고 조금더 확장된 기능이라면 세로에서 FACE ID인식은 물론 스마트 키보드 폴리오를 같이 사용중일때는 아이패드가 가로로 놓여지게 되는데 그 가로 상태에서도 FACE ID 인식이 아주 잘됩니다.( 전방향인식 )


또한 디스플레이 성능도 향상되었고 A12X Bionic 칩이 탑재되어 초당 5조 번의 연산과 첨단 머신 러닝이 가능한 Neural Engine을 탑재하고 있다고 합니다. 쉽게 말해 대부분의 노트북 PC보다 빠르다는 의미입니다.




이번 애플펜슬 2세대는 엉덩이 쪽에 충전하는 곳이 없습니다. 그냥 아이패드 상단에 가져다 대면 자석으로 딱 붙습니다. 그리고 충전이 되죠. 그리고 자동으로 페어링이 됩니다. 그리고 애플펜슬의 하단을 이중탭하여 기능 전환이 가능합니다 ! 저는 또한 윌리스 매장에서 아이패드 필름을 구입했는데, 질감이 거칠어 마치 팬슬로 쓰면 연필로 쓰는 듯한 느낌이 듭니다. 여러분들도 이런 감성을 좋아하시면 필름하나 구입하시는 것도 나쁘지 않을 것 같습니다!

▶︎▶︎▶︎아이패드필름구입처


마지막으로 "스마트 키보드 폴리오" 입니다. 가격은 만만치 않지만 역시 아이패드는 키보드가 있으면 더 편리하며 그 성능을 더 발휘? 하는 듯한 느낌이 듭니다. 끼우지 않아도 자석으로 되어있어 아이패드를 가져다대면 딱 붙습니다. 그리고 스탠딩하는 순간 자동으로 페어링됩니다. 









또한 키보드는 충전이 필요없습니다. 아이패드 후면에 3개의 점에 착 하고 붙이는 순간 데이터 통신과 충전이 양방향으로 이루어집니다. 그래서 제 생각엔 그러면 팬슬,키보드 전원을 둘다 패드가 주입해주니 배터리가 빨리 소모되는거 아닌가 했는데, 생각보다 그렇지 않습니다. 배터리 빨리 소모 안됩니다.


command 키를 꾹 누르고 있으면 해당 앱에서 사용가능한 단축키 목록도 볼 수 있습니다.








사용후기


가격이 일단 크게 부담됩니다. 아이패드 프로 3세대 11인치 256이 1,190,000원, 애플팬슬 2세대 159,000원, 스마트 키보드 폴리오 11인치 219,000원 다해서 1,500,000원이 넘는 금액입니다. 하지만 구입후 2주 동안 사용하면서 당연히 그 값은 한다고 생각합니다. 코딩 제외하고는 거의 아이패드로 업무를 봐도 무방하다고 봅니다. 여러분들도 기회가 되신다면 꼭 구매하셔서 사용해보셨으면 좋겠습니다!


posted by 여성게
:
일상&기타/Apple 2019. 2. 9. 22:43



Apple - AirPods 에어팟 구매후기 !



제가 사는 곳이 강북구인데, 강북구 중에서도 조금 외진곳이라 교통이 아주 불편했습니다. 물론 이 주변사는 분들과 강북구를 사시는 분들은 다들 느꼈을 것이 입니다. 다행이 우이신설이라는 무인지하철이 생겼습니다. 그래서 나름 교통이 편해졌지만 직장이 분당이라 아침마다 4번의 환승과 지옥철을 타고 출근을 하게됩니다.(아침에 우이신설타시는 분은 제 심정을 이해할듯..) 그러면서 느낀것을 이어폰을 끼니 사람들에게 치여서 자꾸 빠지는일이 생기게 되더라구요. 그래서 구매 결심하게 된것이 에어팟입니다. 이어폰이 없다는 점이 아주 매리트가 있더군요. 하지만 너무 늦게 구입한 탓일까요. 정식 대행판매 매장에는 전부다 품절이더군요.. 그래서 국내에 하나뿐이 신사동 애플가로수샵에 가서 여자친구것과 제것 두개를 구입했습니다.



에어팟외형

▶︎▶︎▶︎애플공식홈페이지






위 사진과 같이 에어팟은 선이 없습니다. 그리고 에어팟 충전기에 넣어 충전이 가능합니다. 또한 AirPods은 충전 케이스에서 꺼내는 즉시 바로 켜져 iPhone, Apple Watch, iPad, Mac에 연결됩니다. 그리고 이어폰을 귀에 꽂자마자 오디오가 자동으로 재생되며, 빼는 순간 일시정지가 되며. 볼륨 조절, 곡 변경, 전화 통화, 심지어 길 안내까지. 이어폰을 두 번 탭하기만 하면 Siri를 호출할 수 있습니다.








위에서 이야기했듯이 이어폰이 귀에 꽂혔는지를 감지하기 때문에 이어폰은 끼고 빼고를 인식할 수 있습니다.(물론 설정으로 끌수도 있습니다.) 또한 에어팟은 Apple이 자체 개발한 W1 칩으로 구동되어서 극도로 효율적인 무선기술을 선보이며 배터리 수명관리까지 탁월하다고 합니다.(한번 충전으로 5시간정도의 재생이 가능하다고 합니다.) 그리고 음성 감지 가속도계가 탑재되어 있어 대화 중일때는 인식하며, 빔포밍 마이크로 외부 소음을 필터링하고 내 목소리를 상대방에게 전달한다고 합니다.






박스와 제품구성입니다. 처음에 에어팟을 연결하기 위해서는 연결을 원하는 기기 근처에서 에어팟 케이스를 열어주면 기기에서 연결관련된 창이 뜨게됩니다. 그 창이 뜨면 등록하시면 됩니다...


저는 비싸게 구입한 만큼 엘라고 에어팟 케이스와 에어팟이 철가루가 많이 쌓인다고 해서 엘라고 철가루 방지 스티커까지 구입해서 부착하였습니다. 오래오래 깨끗히 사용하실 분이라면 애플 주변제품으로 유명한 엘라고에서 케이스와 철가루 방지 스티커를 구매하실 것을 추천드립니다~!








사용후기


에어팟! 정말 대만족입니다. 이어폰의 선이 없다는 것이 이렇게 편하다는 걸 다시 한번 느낍니다... 아주 편합니다 ! 그리고 에어팟 기능중 하나인 이중탭을 설정에서 원하는 기능으로 변경가능합니다.

에어팟 이중탭 설정

에어팟을 블루투스로 연결한 후에 아이폰 설정>Bluetooth>AirPods 옆에 느낌표 버튼>AirPods 이중 탭 에서 왼쪽,오른쪽 이중 탭의 기능설정이 가능합니다. 그리고 자동으로 착용감지를 비활성화하면 에어팟 한쪽을 빼고 계속 재생이 됩니다.(처음에는 한쪽을 빼면 재생이 멈춰서 아주 불편했는데 역시나 설정이 있더군요)


posted by 여성게
:
일상&기타/Apple 2019. 2. 9. 22:07



Apple - Magic Keyboard & Magic Mouse 제품 후기 !



정말 고민고민하다 구매한 제품 리뷰입니다... 워낙 맥북프로를 고가를 주고 샀기에 이 두개를 사야하나 참 고민을 많이 했습니다. 정말 애플제품은 제품자체도 비싸지만 주변제품 또한 만만치 않습니다...사람인지라 애플제품에 타제품 주변기기를 사기도 그렇고 정품만 사니 정말 돈이 많이 나가더군요 ㅠ_ㅠ



Magic Keyboard & Magic Mouse

▶︎▶︎▶︎애플 공식홈페이지




Magic Keyboard & Mouse 외형



▶︎▶︎2018년 맥북프로 15인치구매 후기 에서도 얘기했듯이 제가 굉장히 다크한 사람이라 스페이스 그레이로 구입했습니다..(사실 그레이보단 검은색에 가까움, 맥북프로 스페이스 그레이랑 색 차이가 엄청 납니다.)

그런데 이게 왠일;; 색이 진하다고 3만원이 더 비쌉니다... 하지만 구입





후면부는 이미지와 같이 충전선을 꼽을 수 있는 곳과 전원키고 끄는 버튼이 있습니다. 근데 매직 마우스에 잠든 맥북을 깨우는 기능이 있을까요? 간혹 마우스 전원을 켜놓은 상태에서 가방에 넣어놓면 지옥철에서 클릭이 눌렸는지, 맥북을 꺼내보면 팬돌아가는 소리와 함께 손난로가 되어있을 때가 있습니다. 다들 마우스 안쓰실때는 전원을 꺼놓으시길...









제품 구성은 마우스와 전원 충전용 케이블 하나들어있습니다.





Magic Keyboard 숫자패드까지 있는 것입니다. 디자인은 뭐 애플이니 전체적으로 깔끔합니다.








상단에 전원충전 포트와 전원 스위치가 존재합니다.





몸체가 살짝 높아지는 구조가서 장시간 사용해도 손목에 무리가 덜 가는 것 같습니다.





박스와 키보드 사진입니다. 그런데 박스에는 실버인데 왜 키보드는 스페이스 그레이냐구요? 다음 사진에 진실이 있습니다.





바로 키스킨입니다. 저는 손에 땀과 기름이 많은 지라... 오래쓰기 위해 키스킨을 구입했는데 스페이스 그레이와 동일한 색과 모양의 키스킨이 있길래 바로 사버렸습니다...

박스 내부에는 역시 충전케이블이 하나 들어가있습니다.


▶︎▶︎▶︎키스킨 구매 홈페이지








사용후기

일단 전체적으로 다 만족합니다. 특히나 매직마우스는 트릭패드로 할 수 있는 대부분의 모션 동작이 가능하기 때문에 아주 편리한 것같습니다. 처음에는 클릭감이 적응이 안됬지만 사용하다보면 적응이 되더라구요. 마우스 꼭 강추입니다!

하지만 키보드 저처럼 개발하시면서 듀얼모니터를 사용하시는 분은 꼭 추천드립니다. 하지만 듀얼모니터를 잘 사용하시지 않으시는 분은 굳이 구매를 안하셔도 될듯합니다....그러면 짐이 되더라구요. 이상 Magic Keyboard & Magic Mouse 구매 후기였습니다.


posted by 여성게
:
일상&기타/Apple 2019. 2. 9. 21:33

Apple - 2018 MacBook Pro 15inch 리뷰!,맥북프로



작년 하반기에 구입한 2018맥북프로 15인치 리뷰를 지금 쓰게되내요.. 사실 좋지만은 않은 일도 있었기에 이제서야 리뷰를 올리게 됩니다. 운이 좋지 않게도 2017년맥북프로 15인치를 구입하고 2주도 되지않아서 불량판정이 나는 바람에 교환을 하게된 제품이 2018년맥북프로 15인치입니다.(2017년형을 막바지에 세일할때 구입했기에 추가비용을 지출하고 2018년형으로 교환하였습니다 ㅠ_ㅠ) 솔직히 불량이라는게 어쩔 수가 없는 부분인 것 같습니다. 


우선은 맥북을 구입하게 된 계기는 직업 자체가 컴퓨터를 사용하는 일이고, 웹프로그래밍을 하는 직업이다보니 쭉 윈도우환경 PC만 사용하다가 유닉스환경에 익숙해자라는 생각과 맥북이 그렇게 좋다라는? 소문에 구입하게 되었습니다 :) 하지만 가격은 나쁜 가격....하지만 그만치 가치는 있다고 생각이 듭니다.



우선 2018년 맥북프로 15인치의 기본사양입니다.


▶︎▶︎▶︎애플 공식홈페이지





우선 2017년맥북프로와 비교해서 CPU 코어수가 늘어났습니다.(i7 6core) 전체적으로 디스플레이나 등등의 성능이 조금씩은 향상이 되었을 것이라 생각이 듭니다.









다음은 맥북프로 외형입니다.




역시 애플의 장점이라면 큰 장점인 깔끔한 디자인입니다.(개인적으로 밝은톤보다는 어두운톤을 좋아해서 스페이스 그레이를 구매) 그리고 2017년제품과 동일하게 USB-C Type 4개의 포트로 구성되어 있고 터치바가 깔려있습니다. 솔직히 터치바가 편한지는 모르겠습니다. 보통 코딩을 할때 키보드를 보지 않고 화면을 보고 자판을 치는데 ESC키를 누르고 싶어도 손에 만져지는 것이 없어서 ESC가 맞나 눈으로 확인하는 과정이 생겨버렸습니다.....물론 편하신분들도 계시겠죠.. 저는 불편했습니다.ㅠㅠ





제품구성



요로코롬 3가지로 구성이 되어있습니다. 진짜인지 확인해볼까요?








이렇게 제품구성이 되어있습니다.(밑에 사진은 이미 개봉 이후여서ㅠ_ㅠ) 마지막으로 제 맥북사진입니다. 외형보호를 위해서 물로 붙이는? 무광 필름을 부착하였습니다. 필름은 명동 프리스비에서 구매하였고 면당 5천원? 해서 총 15000원주고 부착까지 하였습니다. 보통 숙련되지 않은 분들은 반이상은 필름 붙이다가 망치고 다시 구매하시는 분들이 많을 겁니다. 그 중 한명이 저라서...저는 비싼 돈을 주고 필름을 사고 거기에 부착비 15000원을 냈습니다. ㅠㅠ 사실 맥북이 노트북 가격자체도 비싼데 마우스,키보드,슬리프,필름,키스킨 등등... 비싼 노트북을 보호하고 편하게 쓰기 위한 주변제품들 가격도 만만치 않습니다. 여튼 필름부친 외형입니다. 유광도 있고 무광도 있는데 저는 갠적으로 무광을 좋아해서 무광을 선택! 



▶︎▶︎▶︎ 프리스비 명동점



필름을 구매하면 겉면은 물론 옆면, 안쪽 키패드 양쪽과 터치바까지 들어있습니다. 전에는 옆면에 필름을 붙이면 잘 떨어져서 부착을 안해준다고 했는데, 이번엔 돈도 아깝고 해서 부착요청을 했고, 부착후 사용해도 떨어지질 않더라구요. 이러한 클레임이 많아서 신경써서 만든듯 합니다.








2018년 맥북프로는 무엇이 달라졌을까요?


그렇다면 이번 리뷰의 핵심은 터치바 맥북프로 3세대 제품으로도 볼 수 있는 이 제품이 기존의 단점들을 어떻게 보완했고, 기존의 장점들을 얼마나 잘 발전시켰는지가 될 것이다. 여기서는 2018 맥북프로가 기존의 맥북프로에 비해 달라진 점들을 이런 카테고리에 맞춰서 간략하게 평가해보도록 하겠다.

 

먼저 이번 맥북프로의 가장 큰 변화라고 할 수 있는 성능 부분을 살펴보자. 위에서 지적한 것과 같이 인텔맥의 성능 향상은 인텔, GPU의 경우 AMD 칩의 성능 향상에 기인하는 것이고 애플이 직접적으로 이를 통제할 방법은 없다. 다르게 말해서 연산성능 면에서 다른 PC 제품들과 맥이 차별화되는 부분은 거의 없다고 봐도 좋다. 하지만 맥북프로는 macOS 플랫폼을 사용하는 사용자들에게 모바일에서 최고의 성능을 제공한다는 데 의미가 있고, 그런 관점에서 봤을 때 이번 맥북 프로는 상당히 큰 성능 향상을 제공한다.



AMD가 실로 오랜만에 CPU 시장에 던진 폭탄에 반응한 인텔은 드디어 소비자용 CPU의 코어 수를 늘리기 시작했고, 2018 맥북프로 역시 그 수혜를 입었다. 13인치 제품의 경우 쿼드코어가 채택되었고, 15인치 제품의 경우 헥사코어가 채택되어 각각 기존에 비해 2배, 1.5배의 코어 수 향상을 이뤄냈다. 물론 제조공정이 크게 변하지 않았고 코어 수를 늘린 것이므로 전력 소모, 발열이 늘어났고, 프로그램들이 멀티코어를 완벽하게 활용하지 못하기 때문에 순수하게 표기 클럭 * 코어 수로 산술적으로 계산한 만큼의 성능 향상을 기대할 수는 없지만, 근래 들어 가장 큰 폭의 성능 향상으로 봐도 틀리지 않다. 하지만 위에서 언급한 발열 문제는 한참 이슈가 되었고, 애플의 패치로 어느 정도 진화되긴 했지만 여전히 톺아볼 부분이 있어 다음 편에서 좀 더 자세히 다루도록 하겠다.



 

다음으로 살펴볼 부분은 T2 칩에 관련된 부분이다. 기존의 터치바 맥북 프로는 사용자의 지문 정보를 저장할 보안 공간을 포함하고, 터치바와 Touch ID, FaceTime 카메라를 통제하는 프로세서인 T1 칩을 탑재하고 있었다. 이를 통해 Touch ID와 FaceTime 카메라에 좀 더 높은 수준의 보안을 제공해준다는 데 의미가 있다. 하지만 아이맥 프로에 최초로 들어가고 이번 맥북 프로에 탑재된 T2 칩은 좀 더 많은 기능을 포함하고 있다. 기존에는 별도로 있던 SSD 컨트롤러를 T2 칩 내부로 통합했다. 또, T2 칩은 보안 부팅은 물론 하드웨어에 내장된 암호화 키를 이용해 실시간 암호화를 수행하는데 이는 운영체제조차 보지 못하는 레벨에서 이뤄지는 하드웨어 암호화로, 사용자는 실시간으로 일어나는 암호화, 복호화를 전혀 느낄 수 없는 수준에서 이뤄진다. 이런 추가적인 기능들이 들어가면서 T2 칩에는 1GB의 LPDDR4 메모리가 포함되어 있는데, 메인 메모리에는 아직도 LPDDR4 메모리가 사용될 수 없다는 점이 아이러니하다.

 

15인치 맥북 프로의 경우 참다 못한 애플이 DDR4L을 탑재하는 것으로 방향을 선회했다. 따라서 LPDDR3의 한계였던 16GB보다 더 많은 용량의 메인 메모리 구성이 가능해졌을 뿐 아니라 더 높은 속도로 메모리가 동작한다. 하지만 작년에 애플이 DDR4L이 아닌 LPDDR3를 탑재했었던 것에서 알 수 있듯이 이 경우 소모 전력이 늘어나게 되는데, 실제로 데이터를 읽고 쓸 때의 전력 차이보다는 슬립 상태에서의 전력 차이가 상당하다. 애플은 더 강력해진 칩과 DDR4L을 탑재하고도 배터리 성능을 유지하기 위해 더 많은 용량의 배터리를 탑재하는 것으로 타협했다. 13인치 모델의 경우 여전히 LPDDR3 메모리를 탑재하고 있고 최대 구성 가능한 메모리 용량 역시 16GB이다. 다만 13인치 모델의 경우 컴퓨팅 파워가 15인치 모델에 비해 낮고, 실제로 구매하는 사용자가 기대하는 역할 역시 다르다 보니 이것이 큰 문제가 될 것같지는 않다.

 

디스플레이 역시 기존의 훌륭한 디스플레이를 유지하면서 아이패드 프로(9.7인치 이후 모든 아이패드 프로 제품), 아이폰(아이폰 8 시리즈, 아이폰 X)에 도입되었던 True Tone 기능을 추가했다. 이 기능은 주변 빛에 맞춰 화면의 화이트포인트를 조절해주는 기능으로, 서로 다른 화이트 포인트에 번갈아가며 적응해야하는 눈의 피로를 덜어줄 수 있는 기능으로 사용자 경험에 큰 영향을 미칠 수 있는 기능이다. 소소하지만 이런 True Tone 기능은 메인 디스플레이 뿐 아니라 터치바에도 적용된다.

 

기존 모델에서 단점으로 지적되었던 키보드 역시 개선되었다. 애플은 키 아래에 실리콘으로 된 막을 추가하여 키 소음을 줄이고 먼지 유입을 방지하여 버터플라이 방식 키보드의 문제점으로 지적된 부분을 고치려 했다. 물론 애플의 이런 조치가 효과를 발휘하는지를 지켜보려면 조금 더 시간이 필요하긴 하겠다. 이 외에도 스피커 성능이 더 높아지는 등 소소한 개선점들이 있었다.

 

애플은 이번 맥북프로 업데이트에서 기존에 단점으로 지적받았던 부분을 일부 수정했고, 큰 폭의 성능 향상을 가져왔다. 또, T2 칩을 통해 사용자에게 더 높은 보안성을 제공하게 된 것 역시 긍정적인 부분이다. 하지만 방열 설계의 큰 변경이 없는 상태에서 더 높은 발열을 내는 프로세서의 탑재 등은 잠재적인 불안요소이다. 이런 점들에 대해서는 다음 편에서 좀 더 자세한 실험과 함께 다루도록 하겠다.


▶︎▶︎▶︎출처 




사용후기


일단 크게 만족합니다. 디자인도 아주 깔끔하고 성능? 여느 노트북과 비교하여 떨어지지 않습니다. 무엇보다 좋은 것은 좋은 성능, 깔끔한 디스플레이도 좋지만 가장 좋았던 점은 서버환경과 거의 동일하게 개발을 진행할 수 있다는 점입니다. 터미널을 직접 다루다 보니 서버 세팅할 때도 한결 수월해졌습니다. 저처럼 개발자 이신분은 맥북 한번 써보시길 추천드립니다! 


아 ! 그리고 하나 맥북 구매하시는 분들에게 조언이라면 조언을 해드리자면 SSD는 512로 업그레이드 하시길 추천드립니다... 생각보다 256은 부족하내요 ㅠㅠ 사실 램도 상향할수는 있지만 16G로도 왠만한건 충분하다 생각들지만 SSD는 늘리는 것을 추천드립니다. 만약 가격이 부담되신다면 외장 SSD를 넉넉하게 사셔서 사용하셔도 좋을 것 같습니다.(삼성꺼 기준 외장 SSD 512가 10만원대 였던걸로 기억합니다.)




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. 14:59

JPA - @NamedQuery , 정적 쿼리



JPA는 크게 동적쿼리와 정적 쿼리로 나뉜다. 그 중에 정적쿼리(@NamedQuery)는 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해둔다. 따라서 오류를 빨리 확인할 수 있고, 사용하는 시점에는 파싱된 결과를 재사용하므로 성능상 이점도 있다. 그리고 정적쿼리는 변하지 않는 정적 SQL이 생성되므로 데이터베이스의 조회 성능상 최적화에 도움이된다. 왜냐하면 데이터베이스는 내부적으로 한번 사용된 쿼리는 캐싱? 해놓기 때문에 다음에 동일한 쿼리가 있을 경우 재사용하기 때문이다.




@NamedQuery



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
@Entity
@Table(name = "MEMBER_JPQL")
@Getter
@Setter
@ToString
@NamedQuery(
        name="MemberJPQL.findByName",
        query="select m from MemberJPQL m where m.username = :username"
)
public class MemberJPQL {
    
    @Id
    @Column(name = "MEMBER_JPQL_ID")
    @GeneratedValue(strategy=GenerationType.TABLE, generator = "MEMBER_JPQL_SEQ_GENERATOR")
    @TableGenerator(
            name="MEMBER_JPQL_SEQ_GENERATOR",
            table="MY_SEQUENCE",
            pkColumnName="SEQ_NAME"//MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
            pkColumnValue="MEMBER_JPQL_SEQ"//SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
            allocationSize=1
    )
    private Long id;
    
    @Column(name = "USERNAME")
    private String username;
    
    @Column(name = "USERAGE")
    private int age;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private TeamJPQL team;
    
    @OneToMany(mappedBy = "member")
    private List<OrderJPQL> orders = new ArrayList<OrderJPQL>();
}
 
 
 
/*
 * 정적쿼리 @NamedQuery
 * 애플리케이션 로딩 시점에 JPQL 문법을 체크하고 미리 파싱해둔다. 따라서 오류를 빠르게 확인할 수 있고,
 * 사용하는 시점에는 미리 파싱된 결과를 재사용하므로 성능상 이점이 있다. 그리고 Named쿼리는 변하지 않는
 * 정적쿼리가 생성되므로 데이터베이스 조회 성능 최적화에 크게 도움된다.
 */
    public void namedQuery() {
        List<MemberJPQL> member = em.createNamedQuery("MemberJPQL.findByName",MemberJPQL.class)
                              .setParameter("username""여성게1")
                              .getResultList();
        
        System.out.println("================namedQuery=================");
        
        for(MemberJPQL m : member) {
            System.out.println(m.toString());
        
        }
        System.out.println("================namedQuery=================");
        
    }
}
cs




위 소스코드에도 보이지만 정적쿼리 네이밍규칙?은 아니지만 엔티티명.정적쿼리명으로 네이밍규칙을 지키는 것이 좋다. 엔티티마다 같은 필드명이 존재할 수도 있으니 유니크한 이름을 위해 엔티티명을 필히 붙여주는 것이 좋다. 그리고 파라미터 바인딩 변수도 엔티티의 필드명과 동일하게 하는 것이 사용자로 하여금 편리함을 줄 수 있다.


위와 같이 어노테이션으로 정적쿼리를 작성할 수도 있지만, XML파일로 정적쿼리를 한번에 다 몰아서 작성할 수 있다. 두가지는 각각 장단점이 존재한다. XML은 복잡하지만 자바언어에서 여러줄의 스트링을 다루기 힘든 반면에 xml은 여러줄의 로우를 다루기 편하고, 한곳에 모든 정적쿼리를 모아담을 수 있다. 하지만 정적쿼리가 많지 않다면 필자는 어노테이션으로 정의해도 크게 문제 없다 생각한다.


마지막으로 한 엔티티에 여러 정적쿼리를 어노테이션으로 다루고 싶다면,


@NamedQueries({

@NamedQuery....

,@NamedQuery....

,@NamedQuery....

})


처럼 여러개 작성이 가능하다.



posted by 여성게
:
Web/JPA 2019. 2. 9. 14:49

JPA - JPQL 서브쿼리 (객체지향 쿼리 언어),컬렉션 식


JPQL도 서브 쿼리를 지원하지만 몇 가지 제약사항이 존재한다.


-WHERE, HAVING 절에서만 사용할 수 있고, SELECT,FROM절에서는 사용할 수 없다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * 서브쿼리 - SQL과 다르게 where,having 절에서만 사용가능하다.
 */
public void subQuery() {
    String jpql = "select m from MemberJPQL m where m.age > (select avg(m2.age) from MemberJPQL m2)";
    TypedQuery<MemberJPQL> query = em.createQuery(jpql,MemberJPQL.class);
    List<MemberJPQL> teams = query.getResultList();
 
    System.out.println("================subQuery=================");
 
    for(MemberJPQL m : teams) {
        System.out.println(m.toString());
    }
    System.out.println("================subQuery=================");
}
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
/*
 * 서브쿼리 함수
 * [NOT] EXISTS (subquery) - subquery에 결과가 존재하면 참이다. NOT은 반대
 * {ALL | ANY | SOME } (subquery) - ALL 서브쿼리의 모든결과가 참이면 참, ANY 서브쿼리의 결과중 아무거나 참이면 참, 
SOME == ANY(반드시 비교연산자와 사용)
 * [NOT] IN (subquery) - 서브쿼리 결과 중 하나라도 같은 것이 있으면 참이다.
 *                        - 서브쿼리가 아닌 곳에서도 사용가능 ~ IN ('여성게1','여성게2')
 */
public void subQueryFunction() {
    String jpql = "select m from MemberJPQL m where m.age < ALL (select avg(m2.age)"
                +"from MemberJPQL m2)";
 
    TypedQuery<MemberJPQL> query = em.createQuery(jpql,MemberJPQL.class);
    List<MemberJPQL> teams = query.getResultList();
 
    System.out.println("================subQueryFunction=================");
 
    for(MemberJPQL m : teams) {
        System.out.println(m.toString());
    }
 
    System.out.println("================subQueryFunction=================");
 
}
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
/*
 * 연관관계 컬렉션에 사용할 수 있는 수식? 컬렉션 식이다.
 * is [not] empty - 연관관계에 있는 컬렉션이 비어있는가?
 * {엔티티나 값} [not] member of {컬렉션 값 연관경로} - 엔티티나 값이 컬렉션에 포함되어있는가?
 */
public void collectionFunction() {
    /*String jpql = "select t from TeamJPQL t where t.members is not empty and t.name ='티스토리1'";*/
    String jpql = "select t from TeamJPQL t where :memberParam member of t.members";
    TypedQuery<MemberJPQL> query2 = em.createQuery("select m from MemberJPQL m where m.username='여성게1'",MemberJPQL.class);
    MemberJPQL member = query2.getResultList().get(0);
 
    TypedQuery<TeamJPQL> query = em.createQuery(jpql,TeamJPQL.class);
    query.setParameter("memberParam", member);
    List<TeamJPQL> teams = query.getResultList();
 
    System.out.println("================collectionFunction=================");
 
    for(TeamJPQL m : teams) {
        System.out.println(m.toString());
    }
 
    System.out.println("================collectionFunction=================");
 
}
cs






posted by 여성게
: