오늘은 Spring boot로 jooq를 사용하는 방법을 간단하게 살펴보도록 하자.
jooq를 간단하게 설명하자면
Java Object Oriented Querying
의 약자로 자바코드로 Sql을 작성할 수 있게 도와주는 프레임워크이다. 이 또한 자바 코드를 작성하기 때문에 타입세이프하다.
JPA/hibernate를 사용하다보면 ORM으로 사용하기 어려운부분들을 jooq를 사용해서 해결하는 방법도 나쁘지 않다. 이와 비슷한 타입세이프한 프레임워크로는 ORM을 사용할 때 자주 등장하는 빌더로
QueryDsl
,
jinq
도 있다.
그렇긴 하지만 jooq의 경우에는 ORM은 아니니 주의(?)해야 한다. JPA와 같이 사용할 수는 있는거 같다.
개발자 마음이긴 하지만 JPA(ORM)을 사용하다보면 복잡한 쿼리 혹은 ORM으로 해결할 수 없는 쿼리가 가끔 등장한다. 그래서 그 해결방법으로는
mybatis
, Spring에서 제공해주는
JdbcTemplate
,
jpa native query
, Spring의
Query
어노테이션 등 다양한 방법으로 해결가능하다. 그 중에 하나도 이번시간에 살펴볼 jooq도 포함되어 있다.
jooq는 Spring boot에서도 자동설정을 지원해준다. Spring boot 1.3부터 지원해주니 참고 하면 되겠다.
그럼 설정 방법과 사용법을 간단하게 살펴보도록 하자!
spring boot initializer 해서 jooq를 선택해도 된다.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
일단 기본적으로는 starter jooq와 각자가 사용하는 데이터베이스의 드라이버를 선택하면 된다. 그리고 해줘야할 부분이 있는데
maven
에 plugin을 설정 해줘야 한다. 필자가 생각하는 jooq의 단점으로 굳이 뽑자면 코드를
generate
해야 된다. 물론 Querydsl도 마찬가지지만..
하지만 꽤나 코드가 길어서 생략하고 샘플코드는
여기에 있으니 참고하면 되겠다.
사용할 테이블이 존재해야 하니 classpath에
schema.sql
파일을 생성하고 다음과 같이 작성하자.
DROP TABLE IF EXISTS PRODUCT;
DROP TABLE IF EXISTS CUSTOMER;
CREATE TABLE CUSTOMER(
ID NUMBER(7) NOT NULL PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (20) NOT NULL,
EMAIL VARCHAR (50),
CONSTRAINT CUSTOMER_PK PRIMARY KEY (ID)
);
CREATE TABLE PRODUCT (
ID NUMBER(7) NOT NULL PRIMARY KEY AUTO_INCREMENT,
PRODUCT_NAME VARCHAR (20),
CUSTOMER_ID NUMBER(7),
CONSTRAINT PRODUCT_PK PRIMARY KEY (ID),
CONSTRAINT PRODUCT_FK FOREIGN KEY (CUSTOMER_ID) REFERENCES CUSTOMER(ID)
);
일단 대충 만든 DDL이니 각자가 수정해서 사용해도 된다.
만약 기본 데이터가 필요 하다면 classpath에
data.sql
을 만들어서 초기화 데이터를 작성해도 된다.
사용법은 간단하다. jooq의
DSLContext
인터페이스가 있는데 이것을 사용하면 된다. 구현체로는
DefaultDSLContext
클래스 한개 존재 한다. Spring boot도 이 클래스를 빈으로 등록하니 해당 클래스를 주입받아 사용하면 된다.
@Repository
@Transactional(readOnly = true)
public class CustomerRepository {
private final DSLContext dslContext;
public CustomerRepository(DSLContext dslContext) {
this.dslContext = dslContext;
}
@Transactional
public void save(String name, String email) {
this.dslContext.insertInto(Customer.CUSTOMER)
.columns(Customer.CUSTOMER.NAME, Customer.CUSTOMER.EMAIL)
.values(name, email).execute();
}
public Optional<CustomerDTO> findOne(Integer seq) {
final Map<Record, Result<Record>> recordResultMap = this.dslContext.select().from(Customer.CUSTOMER)
.leftJoin(Product.PRODUCT)
.on(Customer.CUSTOMER.ID.eq(Product.PRODUCT.CUSTOMER_ID))
.where(Customer.CUSTOMER.ID.eq(seq))
.fetch()
.intoGroups(Customer.CUSTOMER.fields());
return getCollect(recordResultMap).findFirst();
}
private Stream<CustomerDTO> getCollect(Map<Record, Result<Record>> recordResultMap) {
return recordResultMap
.values()
.stream()
.map(records -> {
final Record3<Integer, String, String> record3 = records.into(Customer.CUSTOMER.ID, Customer.CUSTOMER.NAME, Customer.CUSTOMER.EMAIL).get(0);
final Integer customerId = record3.value1();
final String name = record3.value2();
final String email = record3.value3();
List<ProductDTO> products = records.sortAsc(Customer.CUSTOMER.ID).into(ProductDTO.class)
.stream()
.filter(productDTO -> productDTO.getId() != null)
.collect(toList());
return new CustomerDTO(customerId, name, email, products);
});
}
위의 코드는 간단하게 사용해본 jooq의 코드이다. 물론 고칠 부분이 조금 있어 보이지만 예제이므로 생략하도록 한다.
insert와 select 모두 일반 sql문과 문법이 비슷하다.
insertInto
메서드도 보이고
leftJoin
,
on
,
where
절도 보인다. sql을 사용할줄 안다면 눈에 익숙한 코드이다. 이렇게 Sql을 자바코드로 작성하므로 오타가 발생할 가능성이 적어졌다. 아마 타입세이프가 최대 장점이지 않을까 생각된다.
한번 테스트를 해보자.
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootJooqExampleApplicationTests {
@Autowired
private CustomerRepository customerRepository;
@Test
public void saveTest() {
customerRepository.save("test", "test@test.com");
final CustomerDTO customerDTO = customerRepository.findByname("test")
.stream()
.findFirst()
.get();//
assertThat(customerDTO.getName()).isEqualTo("test");
assertThat(customerDTO.getEmail()).isEqualTo("test@test.com");
}
@Test
public void findOneTest() {
final CustomerDTO customerDTO = customerRepository.findOne(1).get(); //
assertThat(customerDTO.getId()).isEqualTo(1);
assertThat(customerDTO.getName()).isEqualTo("wonwoo");
assertThat(customerDTO.getEmail()).isEqualTo("wonwoo@test.com");
assertThat(customerDTO.getProducts()).hasSize(4);
}
}
위와 같이 테스트도 잘 되는 것을 확인 했다. 만약 jooq의 셋팅 정보를 바꾸고 싶다면 아래와 같이
Settings
클래스를 빈으로 등록하면 된다.
@Bean
public Settings settings () {
return new Settings().withRenderFormatted(true);
}
이렇게 오늘은 간단하게 jooq를 사용하는 방법을 살펴봤다. 필자도 그냥 사용법만 살펴본거라 아직은 깊이 있게 잘 알지 못한다. jooq 공식 홈페이지에 가면 문서도 잘 되어있으니 관심있으면 한번 살펴보도록 하면 되겠다. 근데 오픈소스도 있긴 한데 Express, Professional, Enterprise가 존재 한다. 이것은 무료가 아니다. 돈내고 사용하니 뭔가 지원을 많이 해주겠지?