잊지 않겠습니다.

* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.



queryDSL과 Spring Data JPA에 대한 소개를 하기 전에 먼저 JPA에 대한 설명을 하도록 하겠습니다.

JPA란?

Java Persistence API의 약자입니다. 
영속성 객체에 대한 Java의 접근 방법을 정의한 API로, 객체를 통한 Persistence 영역의 접근을 제공하고 있습니다. 

객체를 통한 Persistence 영역에 대한 접근을 하기 위해, 사용되는 것이 ORM Framework 들이고, 그중에서 가장 선두를 달리고 있는 것이 Hibernate입니다. 

Hibernate와 JPA간의 차이를 한번 알아보도록 하겠습니다. 


HibernateJPA
DB에 대한 접근(DataSource)SessionFactoryEntityManagerFactory
DB의 query executorSessionEntityManager
INSERT 대응 methodsave | saveOrUpdatemerge
UPDATE 대응 methodupdate | saveOrUpdatemerge
DELETE 대응 methoddeleteremove
query 적용 방법Criteria, HQL (Hibernate query Language), Native SQL
* Criteria : session.createCriteria
* HQL : session.createQuery
* Native SQL : session.createSQLQuery
Criteria, JPQL (java persistence query language), Native SQL
* Criteria의 문법은 Hibernate와 큰차이를 보이고 있음. JPA3.0에서는 기존의 Hibernate와 통일성을 주는 방향으로 변화가능성이 있다고 이야기함
* JPQL의 문법은 HQL과 완전 동일
* JPQL : em.createQuery
* Native SQL : em.createNativeQuery

접근 하는 방식은 거의 1:1로 동일합니다. 다만 query의 적용방법에서 차이를 보이게 되는데요. HQL이 JPA의 표준이 되면서, 기존 Hibernate에서 주로 사용되고 있던 Criteria보다 근간에는 JPQL 또는 HQL을 더 많이 사용하게 되는 추세입니다. 다음 JPA 버젼에서는 Criteria의 문법변경 이야기가 있으니, 그 때는 다시 역전되지 않을까.. 하는 생각을 가지고 있습니다. 

지금부터 소개할 queryDSL과 Spring Data JPA는 JPA를 기반으로 하는 기술입니다. 
소개드릴 queryDSL과 Spring JPA Data는 다음과 같은 장점이 있습니다. 

1. Hibernate와 같은 save, update, delete를 사용할 수 있습니다.
2. type-safe 한 query를 작성 가능합니다.
3. sql 작성자들과 query를 code로 옮기기가 용의합니다.
4. DAO logic의 코드양이 획기적으로 줄어들 수 있습니다.

이번 장에서는 Hibernate, JPA를 기반으로 하는 Model에 대한 library들을 좀 더 알아보도록 하겠습니다. 

queryDSL

.NET에서 linq가 나온 후, linq의 사상을 옮겨온 Framework입니다. type-safe한 query 작성 및 Criteria 보다 보기 편한 query를 만드는 것이 목적입니다.

queryDSL은 query에 대한 Q-Object를 이용해서 마치 sql query문과 비슷하게 query문을 작성할 수 있습니다. 매우 깔끔하게 보이기도 하고, .NET에서 linq를 하던 사람들에게 좀 더 익숙하기도 한 개발 방법입니다. queryDSL을 사용하면 다음과 같은 query 문을 작성해서 처리 가능합니다. 다음은 queryDSL을 이용한 select query입니다. 

List<Customer> result = query.from(customer)
    .where(customer.lastName.like("A%"), customer.active.eq(true))
    .orderBy(customer.lastName.asc(), customer.firstName.desc())
    .list(customer);    

select * from customer where lastname like 'A%' and active = 1 order by lastname asc, firstname desc;

보시면 query문과 매우 비슷한 구조를 가지고 있습니다.

queryDSL과 나중에 소개드릴 Spring Data JPA는 java persisance area 표준에 따르고 있습니다. JPA라고 불리우는 이 표준은 Hibernate가 주축이 되어, Hibernate의 ORM적 구성이 Java의 표준이 되어버린 구성입니다. 지금 구성된 queryDSL은 Hibernate와 같은 JPA 상에서 구성되는 Library입니다. 따라서, Hibernate로 구성된 BookStore가 그대로 사용되어질 수 있습니다. 

개발 순서는 다음과 같습니다. 

1. Hibernate ORM 규칙에 따른 JPA annotation을 이용한 객체 정의
2. maven을 이용한 compile
3. Query Definition을 이용한 query 작성

먼저 1번 항목인 Hibernate ORM 규칙에 따른 JPA annotation을 모두 구성한 프로젝트에 다음 library들을 모두 추가합니다. (version에 유의해주세요!)
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
    <hibernate.version>4.1.10.Final</hibernate.version>
    <hibernate.validator.version>4.3.1.Final</hibernate.validator.version>
    <spring.version>3.2.2.RELEASE</spring.version>
    <querydsl.version>3.1.0</querydsl.version>
  </properties>

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>${hibernate.validator.version}</version>
    </dependency>
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-core</artifactId>
      <version>${querydsl.version}</version>
    </dependency>
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-apt</artifactId>
      <version>${querydsl.version}</version>
    </dependency>
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-jpa</artifactId>
      <version>${querydsl.version}</version>
    </dependency>
    <dependency>
      <groupId>com.mysema.querydsl</groupId>
      <artifactId>querydsl-sql</artifactId>
      <version>${querydsl.version}</version>
    </dependency>

그리고 build > plugins 에 다음 plugin을 추가합니다. 

      <plugin>
        <groupId>com.mysema.maven</groupId>
        <artifactId>maven-apt-plugin</artifactId>
        <version>1.0.4</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>

그리고 command 창에서 mvn compile을 실행해서 code를 compile합니다. 
compile 후, target directory의 generated-sources를 봐보시길 바랍니다. 안에 보시면 Query 코드들이 만들어져 있습니다. 


이제 target/java 에서 우클릭해서, Use Source Code를 선택해주세요.


이제 queryDSL을 사용할 준비를 모두 마쳤습니다. 

기존 코드인 countAll과 search를 한번 비교해보도록 하겠습니다. 

Hibernate Criteria
    @Override
    public int countAll() {
        Session session = sessionFactory.getCurrentSession();
        Long count = (Long) session.createCriteria(Book.class)
                      .setProjection(Projections.rowCount()).uniqueResult();
        if(count == null) {
            return 0;
        }
        return count.intValue();
    }

    @Override
    public List<Book> search(String name) {
        Session session = sessionFactory.getCurrentSession();
        @SuppressWarnings("unchecked")
        List<Book> books = session.createCriteria(Book.class)
                                  .add(Restrictions.like("name", name, MatchMode.ANYWHERE))
                                  .list();
        return books;
    }

queryDSL
    @Override
    public int countAll() {
        HibernateQuery query = new HibernateQuery(sessionFactory.getCurrentSession());
        QBook qBook = QBook.book;
        Long count = query.from(qBook).uniqueResult(qBook.count());
        return count.intValue();
    }

    @Override
    public List<Book> search(String name) {
        HibernateQuery query = new HibernateQuery(sessionFactory.getCurrentSession());
        QBook qBook = QBook.book;
        return query.from(qBook).where(qBook.name.like("%" + name + "%")).list(qBook);
    }

마치 직접 query를 사용하고 있는 것과 비슷한 문법을 보여줍니다. 이 방법은 Hibernate의 Criteria를 사용할 때 발생할 수 있는 property의 직접 타이핑에 의한 에러를 방지할 수 있으며, 개발자들이 보다더 쉽게 query를 작성할 수 있도록 도와주게 됩니다. 
기존의 CUD는 Session의 save, update, delete를 기존과 같이 사용하고, Read method만 queryDSL을 사용해서 개발의 속도를 향상하고, 오타를 방지할 수 있습니다. 

다음은 queryDSL의 예시입니다. (queryDSL homepage에서 보다 많은 예시를 볼 수 있습니다.)

JOIN
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCate kitten = new QCat("kitten");
query.from(cat)
    .innerJoin(cat.mate, mate)
    .leftJoin(cat.kittens, kitten)
    .list(cat);
from Cat as cat
    inner join cat.mate as mate
    left outer join cat.kittens as kitten

Order
QCustomer customer = QCustomer.customer;
query.from(customer)
    .orderBy(customer.lastName.asc(), customer.firstName.desc())
    .list(customer);
from Customer as customer
    order by customer.lastName asc, customer.firstName desc

Group By
query.from(customer)
    .groupBy(customer.lastName)
    .list(customer.lastName);
select customer.lastName
    from Customer as customer
    group by customer.lastName

매우 많은 query 문이 존재하고, sql query로 대부분이 작성 가능합니다. 꼭 한번 사용해보시길 바랍니다. 

하나 더 말씀드린다면, 이 부분은 지금 저희 개발에서 Domain의 main 기술이 될 것입니다. Hibernate를 바로 사용하는 것에 대한 부담을 해결할 수 있습니다. 그리고 query를 만드는 개발 패턴을 좀 더 효율적으로 구성할 수 있다는 장점을 가지고 있습니다. 



Spring Data JPA

Spring Data JPA는 ORM Framework입니다. JPA 기반의 repository를 아주 빨리, 쉽게 개발할 수 있는 방법을 제시하고 있습니다. 지금까지 보던 모든 기술들(JdbcTemplate, Hibernate, myBatis)에서 repository는 객체만 바뀔뿐, 코드의 중복은 계속해서 나타나게 됩니다. 특히 우리가 CRUD라고 부르는 영역의 CUD 코드의 경우에는 거의 대부분이 중복이 되는 경우가 많습니다. 이러한 문제점을 착안하여 Spring Data - JPA project가 시작되게 되었습니다. 

Spring Data JPA는 단독으로 동작하는 ORM Framework가 아닙니다. JPA ORM engine을 기반으로 동작하는 ORM Framework입니다. 여기에서 JPA ORM engine이 될 수 있는 표준적인 JSR 220 을 만족하는 ORM engine은 다음과 같습니다. 

# Hibernate
# Google App Engine for JAVA
# TopLink

Google App Engine이 RDBMS를 완벽하게 지원하지 못하고, TopLink의 경우에는 Oracle에서 판매하는 상용제품이며 Oracle DB에만 특화가 되어있는 ORM입니다. 그렇다면, 실질적으로 지금 사용할 수 있는 선택의 폭은 Hibernate 밖에는 존재하지 않습니다. 지금 상태로는 Spring Data JPA는 Hibernate를 기반으로 동작하는 ORM Framework이다. 라고 생각해주시면 좋을 것 같습니다. 

Spring Data JPA를 사용하기 위해서는 다음을 추가합니다. (Hibernate 프로젝트 생성에서 시작합니다.)

    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>1.3.0.RELEASE</version>
    </dependency>

Spring Data JPA는 Spring 중 다음 jar 들과 연관관계를 갖습니다. 

spring-core, spring-beans, spring-context, spring-orm, spring-aop, spring-tx

이제 JPA의 기반이 되는 Hibernate를 추가해주도록 하겠습니다. 

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>${hibernate.version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>${hibernate.version}</version>
    </dependency>

마지막으로 Spring Framework를 모두다 추가하도록 하겠습니다. 

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>${spring.version}</version>
    </dependency>

이제 pom.xml 설정이 모두 마쳐졌습니다. 

조금 장황할정도로 pom.xml 에 설정이 많이 들어가게 됩니다. 

Spring JPA Data는 Dao/Repository의 반복적인 코드가 될 수 있는 CUD를 자동화된 코드로 지원합니다. Spring JPA Data에서 제공하는 interface는 다음과 같습니다.

# Repository
# CrudRepository
# JpaRepository

이 중에서 우리가 사용할 녀석은 다른 interface를 모두 상속한 JpaRepository<T, C>입니다. JapRepository의 선언은 전에 Hibernate의 GenericDao의 선언과 거의 동일합니다. 단 두번째 인자가 PK에 대한 객체 Type을 넣어주는것만 다릅니다. Book, User, History 모두 int를 사용하고 있기 때문에, Integer를 두번째 Type에 넣어주면 됩니다. 
코드는 Hibernate에서 작성된 annotation이 모두 적용된 entity 객체들을 모두 project에 copy를 합니다. 먼저 단순하게 BookDao interface를 다음과 같이 작성합니다.

public interface BookDao extends JpaRepository<Book, Integer> {

}

interface 구현이 아닌 interface 상속을 통해서 구현이 되어 있는 것을 주의해주세요. JpaRepository<T,C> 의 T에는 Target이 되는 Class가 들어가고, C에는 Target Class의 @Id property의 객체값이 들어갑니다. (int인 경우, Integer)

자. 이제 지금까지 만들었던 BookDaoImpl의 코딩은 모두 끝났습니다. interface에 대한 코딩 작업이 전혀 필요하지 않습니다. 그 이유는 기본적으로 전에 보셨던 GenericDao에 대한 확장과 비슷한 방법입니다. Spring Data JPA는 JpaRepository, Repository, CrudRepository를 상속받은 interface를 모두 찾아내서, 동적인 객체를 생성합니다. 우리가 따로 코드를 만들어줄 필요가 전혀 없는것이지요. GenericDao의 결정판입니다. ^^

이제 테스트 코드를 작성해보도록 하겠습니다. 기존의 테스트 코드에서 약간의 변경만 있으면 됩니다. countAll()을 count()로, add, update를 모두 save로 변경만 시켜주면 테스트 코드가 모두 완성됩니다. 

@SuppressWarnings("unused")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)
@Transactional
public class BookDaoTest {
    @Autowired
    private BookDao bookDao;
    @Autowired
    private HistoryDao historyDao;
    @Autowired
    private UserDao userDao;
    private static Logger logger = LoggerFactory.getLogger(BookDaoTest.class);

    @Before
    public void setUp() {
        logger.trace("==== setUp started");
        assertThat(bookDao, is(not(nullValue())));
        historyDao.deleteAll();
        userDao.deleteAll();
        bookDao.deleteAll();
        assertThat(bookDao.count(), is(0L));
        logger.trace("=== setUp ended");
    }

    @Test
    public void saveAndCount() {
        long currentCount = bookDao.count();
        long index = 0L;
        List<Book> books = getBooks();
        for(Book book : books) {
            bookDao.save(book);
            index++;
            assertThat(bookDao.count(), is(currentCount + index));
        }
    }

마지막으로 applicationContext.xml을 구성합니다. applicationContext는 지금까지 보던것과 약간 많이 다릅니다. 구성된 applicationContext.xml 입니다. 

  <context:property-placeholder location="spring.properties" />
  <bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
    <property name="driverClass" value="${connect.driver}" />
    <property name="jdbcUrl" value="${connect.url}" />
    <property name="username" value="${connect.username}" />
    <property name="password" value="${connect.password}" />
    <property name="idleConnectionTestPeriodInMinutes" value="60" />
    <property name="idleMaxAgeInMinutes" value="240" />
    <property name="maxConnectionsPerPartition" value="30" />
    <property name="minConnectionsPerPartition" value="10" />
    <property name="partitionCount" value="3" />
    <property name="acquireIncrement" value="5" />
    <property name="statementsCacheSize" value="100" />
    <property name="releaseHelperThreads" value="3" />
  </bean>
  <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="dataSource" ref="dataSource" />
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
  </bean>
  <tx:annotation-driven transaction-manager="transactionManager" />
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="packagesToScan" value="xyzlast.bookstore.jpa.bookstore01.entities"/>
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter" ref="hibernateVendor"/>
    <property name="jpaPropertyMap" ref="jpaPropertyMap"/>
  </bean>  
  <util:map id="jpaPropertyMap">
    <entry key="hibernate.dialect" value="${connect.dialect}" />
  </util:map>  
  <bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="true"/>
  </bean>
  <jpa:repositories base-package="xyzlast.bookstore.jpa.bookstore01.repository" transaction-manager-ref="transactionManager" />

@Configuration을 통해서, ApplicationConfiguration이 어떻게 구성이 되어 있는지 한번 알아보도록 하겠습니다.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages= {"com.xyzlast.bookstore.dao"})
@PropertySource("classpath:spring.properties")
@ComponentScan(basePackages = { "com.xyzlast.bookstore.dao", "com.xyzlast.bookstore.services", "com.xyzlast.bookstore.utils" })
public class HibernateBookStoreConfiguration {
    @Autowired
    private Environment env;

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer configHolder = new PropertySourcesPlaceholderConfigurer();
        return configHolder;
    }

    @Bean
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();
        dataSource.setUsername(env.getProperty("connect.username"));
        dataSource.setPassword(env.getProperty("connect.password"));
        dataSource.setDriverClass(env.getProperty("connect.driver"));
        dataSource.setJdbcUrl(env.getProperty("connect.url"));
        dataSource.setMaxConnectionsPerPartition(20);
        dataSource.setMinConnectionsPerPartition(3);
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactory.setPackagesToScan("com.xyzlast.bookstore.entities");
        entityManagerFactory.setDataSource(dataSource());
        entityManagerFactory.setJpaVendorAdapter(hibernateJpaVendorAdapter());
        entityManagerFactory.setJpaProperties(japProperties());
        return entityManagerFactory;
    }

    @Bean
    public Properties japProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
        return properties;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setDataSource(dataSource());
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }

    @Bean
    public HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter =  new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(true);
        return hibernateJpaVendorAdapter;
    }

    // Hibernate를 이용하는 경우, 반드시 HibernateExceptionTranslator가 Bean에 등록되어야지 된다.
    @Bean
    public HibernateExceptionTranslator hibernateExceptionTranslator() {
        return new HibernateExceptionTranslator();
    }
}

설정을 보시면, 이제 기존에 가지고 있던 Hibernate 관련 SessionFactory가 모두 제거가 된 것을 알 수 있습니다. JPA는 java에서 persistance layer에 접근하는 방식을 표준화 한것입니다. 이에 대한 구현체로 가장 유명한 것이 Hibernate가 되기 때문에, Hibernate를 이용한 JPA를 이용하기 때문에, JPA로 Hibernate를 한번 더 싸는 형태로 구성이 되게 됩니다. 

# jpaPropertyMap : jpa의 추가 속성들을 집어넣는 Map입니다.
# hibernateVendor : ORM vendor를 지정하는 객체입니다.
# entityManagerFactory : entityManagerFactory는 Hibernate에서의 SessionFactory와 동일한 역활을 하는 객체입니다. entityManagerFactory를 구성합니다. 속성으로는 Entity로 등록할 객체가 모여있는 package를 지정하고 dataSource, ORM vendor, property를 지정합니다.
# transactionManager : transactionManager를 구성합니다.
# jpa:repositories : repository(dao) 인터페이스가 위치한 package를 지정하고, TransactionManager를 지정합니다.

이제 테스트 코드를 돌려보면 정상적으로 돌아가는 것을 알 수 있습니다. 그리고 BookDao interface의 객체를 System.out.println으로 찍어보면 다음과 같습니다. (HistoryDao 코드만 제외!. HistoryDao의 findByUser, findByBook method는 아직 구현되지 않았습니다.)

org.springframework.data.jpa.repository.support.SimpleJpaRepository@74279e1e

SimpleJpaRepository라는 객체를 Spring Data JPA가 만들어서 @Autowired 해서 사용하는 방식으로 변경하게 됩니다. 이 객체는 CRUD에서 CUD는 이미 지원하고 있고, R의 경우에는 select * from books 정도의 전체를 얻어오는 query를 지원하고 있습니다. 만들어진 interface에 where 조건을 붙인 query를 어떻게 사용하는지 알아보도록 하겠습니다. 


findByXXXX method의 선언

HistoryDao의 findByBook, findByUser의 경우에는 History의 book, user property를 참고해서 where 절을 만들게 됩니다. 이러한 작업 역시 중복 작업이 될 수 있기때문에, 이러한 method에 대해서 Spring Data JPA는 다음과 같은 방법을 이용합니다.

findBy{PropertyName} 을 이용하면, 자동으로 where 조건을 만족하는 구문을 만들어주게 됩니다. 그래서 구현되는 interface는 다음과 같습니다. 

@Repository
public interface HistoryDao extends JpaRepository<History, Integer> {
    List<History> findByUser(User user);
    List<History> findByBook(Book book);
}


이렇게 선언을 해주면, Spring JPA Data가 자동으로 모든 method의 구현코드를 만들어줍니다. GenericDao의 결정판이라고 이야기드린 이유를 아시겠나요? ^^

KEYWORDSAMPLEJPQL SNIPPET
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
BetweenfindByStartDateBetween… where x.startDate between 1? and ?2
LessThanfindByAgeLessThan… where x.age < ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> age)… where x.age not in ?1

Spring Data JPA를 이용한 Sort, Paging


findByXXX method의 경우에는 Sort와 Paging이 확장이 가능합니다. 

기본적으로 Spring Data JPA의 경우에는 method 이름을 이용한 Sort를 지원하고 있습니다. 때에 따라서, Dynamic한 Sort를 넣어줘야지 되는 경우가 종종 존재할 수 있습니다. 이러한 경우, method의 가장 마지막 parameter에 Sort 객체를 넣어주면 다양한 Sort를 지원할 수 있습니다. 기본적인 사용은 다음과 같습니다. 조금 아쉬운것이, Sort의 경우에는 OrderBy의 Property를 문자열로 넣어줘야지 되는 단점을 가지고 있습니다. 약간 아쉬운 점이라고 할 수 있습니다.

public interface BookDao extends JpaRepository<Book, Integer> {
    List<Book> findByNameLike(String name);
    List<Book> findByNameLike(String name, Sort sort);
}

    @Test
    public void findByNameLinkeUsingSort() {
        String bookName = "book";
        String bookNameQuery = "%" + bookName + "%";
        Sort sort = new Sort(Sort.Direction.DESC, "status");
        List<Book> books = bookDao.findByNameLike(bookName, sort);
        for(Book book : books) {
            assertThat(book.getName().contains(bookNameQuery), is(true));
        }
    }

다음은 Paging입니다. 기본적으로 Paging은 Sort의 사용법과 동일하게 parameter의 마지막에 Pageable 항목을  추가해서 Page의 index, size, sort를 모두 지원 가능합니다. Paging을 기본적으로 제공하는 것은 findAll method입니다. findAll에서 Paging을 지원하는 예제 코드는 다음과 같습니다. 

    @Test
    public void findAllUsingPaging() {
        Sort sort = new Sort(Sort.Direction.DESC, "status");
        PageRequest pageRequest = new PageRequest(0, 10, sort);
        Page<Book> books = bookDao.findAll(pageRequest);

        assertThat(books.getNumberOfElements() <= 10, is(true));
        assertThat(books.getNumber(), is(0));
    }


Summary

queryDSL은 type-safe 한 query를 작성할 수 있도록 도움을 줍니다. 개발에 있어서 가장 큰 문제가 될 수 있는 오타에 의한 에러를 막아줄수 있는 확실한 방법이고, query문에 유사한 포멧을 가지게 되기 때문에 SQL 개발자에게도 유용한 개발 방법이 될 수 있습니다. 그리고 Spring Data JPA는 DAO에 대한 코드양을 줄이고, 직관적인 포멧의 코딩을 가능하게 합니다. Spring이나 지금 신생으로 크고 있는 Open Source이기도 하고, 개발에 있어서 많은 도움을 받을 수 있습니다. 

이제 다음장에서는 queryDSL과 Spring JPA Data간의 결합을 시도해볼 예정입니다. 지금까지 구현된 코드를 한번 타이핑을 해보시고 만들어보시길 바랍니다. 감사합니다.






Posted by Y2K
,