오늘은 jinq라는 아이에 대해 살펴보자.
jinq는 c#의 linq의 영감을 얻어 linq 스타일을 그대로 따라 만들었다. jinq는 그냥 평범한 자바 코드로 DB의 데이터를 조회 할 수 있다. QueryDsl과 비슷한 역할을 하는 아이이다. QueryDsl 보다 좋은점은 Q 클래스를 같은 클래스를 만들지 않아도 된다는게 가장 좋은 장점이라고 생각한다. 또한 jpa도 지원하며 구현체로는 Hibernate 와 EclipseLink를 지원하고 scala와 jooq라는 프레임워크도 같이 사용할 수 있다고 한다. jooq도 잠깐 봤었는데 그냥 그렇다.

하지만 java8 의 Stream api와 비슷하기 때문에 java8을 써야 한다. 람다를 써야 더 깔끔하고 보기 좋기 때문에 java8을 써서 하자.

오늘은 간단하게 살펴보자. 필자도 공부하는 중이라..

@Entity
@Data
public class Member {

  @Id
  @GeneratedValue
  private Long id;

  private String name;

  private int age;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "TEAM_ID")
  private Team team;
}

@Entity
@Data
@ToString(exclude = "members")
public class Team {

  @Id
  @GeneratedValue
  private Long id;

  private String name;

  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<>();

}

저런 엔티티가 있다고 가정하자.
그리고 jinq를 사용하기 위한 설정을 해야된다. 설정이라기 보다 디펜더시와 사용하기 위한 setup정도이다.

<dependency>
    <groupId>org.jinq</groupId>
    <artifactId>jinq-jpa</artifactId>
    <version>1.8.13</version>
</dependency>

우리는 jpa를 사용하기 때문에 jinq-jpa를 디펜더시 받으면 된다.

  private EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("default");

  @Test
  public void jinqTest() {
    JinqJPAStreamProvider streams = new JinqJPAStreamProvider(entityManagerFactory);
    EntityManager em = entityManagerFactory.createEntityManager();
   //... 
  }
}

위와 같이 EntityManagerFactory 를 생성후에 JinqJPAStreamProvider에 생성자로 entityManagerFactory를 넣으면 된다. 그리고 그 다음 부터는 jpa 의 기본 코드이다. EntityManagerFactory 통해 EntityManager를 가져오면 된다.
이제 간단하게 문법을 살펴보자. java8을 사용한다면 좀더 쉽게 느껴질 수 있지만 java8의 Stream api와 lambda 를 잘 모른다면 이해가 되지 않을 수 도 있다. 만약 모른다면 그냥 이런게 있다고만 알면 된다.

final Member member = streams
  .streamAll(em, Member.class)
  .where(m -> m.getName().equals("wonwoo"))
  .getOnlyValue();
System.out.println(member);

딱봐도 Stream api와 많이 닮았다. where 절에 보면 member의 name이 wonwoo와 같은 아이를 찾는 것이다. 실제로 JPQL을 보면 아래와 같다.

SELECT
    A 
FROM
    Member A 
WHERE
    A.name = 'wonwoo' 

getOnlyValue 함수 경우에는 결과 값이 두개 이상이면 에러를 내뱉는다. 만약 두개 이상이 나왔을 때는 첫번째 결과가 나오게 하고 싶다면 아래와 같이 하면된다.

final Member member = streams
  .streamAll(em, Member.class)
  .where(m -> m.getName().equals("wonwoo"))
  .findFirst().get();
System.out.println(member);

findFirst 는 Optional를 리턴한다. 물론 get() 함수를 바로 쓰면 안되는건 다 알고 있을 것이다. orElseThrow나 orElse, orElseGet을 쓰길 바란다. get은 되도록이면 쓰지 않는 것이 좋다. 아니면 isPresent 를 사용해서 먼저 검사를 하는 게 좋다.

final List<Member> members = streams
  .streamAll(em, Member.class)
  .where(m -> m.getAge() < 20)
  .toList();
System.out.println(members);

QueryDsl과는 달리 number 같은 경우에는 우리가 흔히 쓰던 부등호를 사용할 수 있다. 위의 쿼리는 나이가 20보다 작은 Member를 불러워는 쿼리가 된다. 실제 JPQL은 다음과 같다.

SELECT
    A 
FROM
    Member A 
WHERE
    A.age < 20

만약 특정 필드만 출력하고 싶다면 select를 이용하면 된다.

final String member = streams
  .streamAll(em, Member.class)
  .where(m -> m.getName().equals("wonwoo"))
  .select(Member::getName)
  .findFirst().get();
System.out.println(member);

JPQL을 보면 다음과 같다.

SELECT
    A.name 
FROM
    Member A 
WHERE
    A.name = 'wonwoo'

또한 동적 쿼리도 가능하다.

String name = "wonwoo";
int age = 20;

JPAJinqStream<Member> stream = streams
  .streamAll(em, Member.class);
if (name != null)
  stream = stream.where( p -> p.getName().equals(name) );

if (age != 0) {
  stream = stream.where( p -> p.getAge() < age );
}
System.out.println(stream.toList());

QueryDsl과 비슷한 방법으로 해당 필드가 있으면 where 넣고 그렇지 않으면 무시한다. 실제 JPQL은 어떤지 보자.

SELECT
    A 
FROM
    Member A 
WHERE
    A.name = :param0 
    AND A.age < :param1

위와 같이 파라미터를 바인딩 한다.

물론 join도 가능하다.

List<Team> teams =
  streams.streamAll(em, Team.class)
    .joinFetchList(Team::getMembers)
    .toList();
System.out.println(teams);

위는 fetch 조인을 사용한 코드이다. 물론 inner join, outer join 등 있을껀 다 있다. 위의 코드는 JPQL이 어떻게 나오는지 보자.

SELECT
    A 
FROM
    Team A 
JOIN
    FETCH A.members B

조인은 나중에 필자도 공부좀하고 다시 살펴보도록 하자.

List<Team> teams =
  streams.streamAll(em, Team.class)
    .where(i -> i.getId().equals(1L) || i.getId().equals(2L))
    .joinFetchList(Team::getMembers)
    .toList();
System.out.println(teams);

만약 조건절에 and말고 or을 사용하고 싶다면 위와 같이 하면 된다. 흠… 조금 아쉽긴하다.
이렇게 jinq에 대해 살짝 알아봤다.

필자도 공부좀 하고 난뒤에 join도 살펴보고 기타 jinq에서 지원하는 함수들을 살펴보자.