최근 인터넷 곳곳에서 ORM 관련 내용들이 많이 쏟아져나오고 있다.
예전에도 Spring 과 Hibernate를 이용해서 내부 프로젝트를 진행 한적이 있었는데, 그때는 단순한것은 Criteria를 사용하고, 조금이라도 복잡한것은 HQL을 사용했던 기억인데 개념적으로 잘 이해되지 않아 초반에 힘들었던거 같은데...
이번에도 역시 많은 삽질을 해야 했다.. 개념 이해 뿐만 아니라 이래저래 많은 변화가 있었던것 같다.
Hibernate에서 JPA, Spring Data JPA, QueryDSL, Spring Data JPA & QueryDSL 등 표준이니 뭐니...
단순 셈플링이다.
아직 알지도 못하는 많은 방법들이 존재해서 뭐가 어떻게 되는건지 심도 있는 학습이 필요할 것 같다.
작은 내부 프로젝트를 진행하려고 하는데 이번 기회에 사용해봐야겠다.
[build.gradle]
apply plugin: 'java'
apply plugin: 'idea'
ext {
javaVversion = '1.7'
servletVersion = '3.0.1'
springframeworkVersion = '4.1.6.RELEASE'
hibernateJpaVersion = '1.0.1.Final'
hibernateEntitymanagerVersion = '4.1.9.Final'
hibernateCommonsAnnotationsVersion = '4.0.1.Final'
hibernateValidatorVersion = '4.3.1.Final'
aspectjVersion = '1.6.8'
queryDslVersion = '3.2.0'
springDataJpaVersion = '1.8.0.RELEASE'
}
List loggerSlf4jAndLogback = [
"ch.qos.logback:logback-classic:1.0.13",
"org.slf4j:jcl-over-slf4j:1.7.5"
]
sourceCompatibility = javaVversion
version = '1.0'
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
distributionUrl = 'http://services.gradle.org/distributions/gradle-2.3-bin.zip'
}
repositories {
mavenCentral()
}
dependencies {
compile loggerSlf4jAndLogback
compile "org.springframework:spring-webmvc:" + springframeworkVersion
compile "org.springframework:spring-orm:" + springframeworkVersion
compile "org.springframework:spring-aspects:" + springframeworkVersion
compile "org.springframework.data:spring-data-jpa:" + springDataJpaVersion
compile 'com.h2database:h2:+'
//compile 'mysql:mysql-connector-java:5.1.23'
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:" + hibernateJpaVersion
compile "org.hibernate:hibernate-entitymanager:" + hibernateEntitymanagerVersion
compile "org.hibernate.common:hibernate-commons-annotations:" + hibernateCommonsAnnotationsVersion
compile "org.hibernate:hibernate-validator:" + hibernateValidatorVersion
compile "com.mysema.querydsl:querydsl-core:" + queryDslVersion
compile "com.mysema.querydsl:querydsl-apt:" + queryDslVersion
compile "com.mysema.querydsl:querydsl-jpa:" + queryDslVersion
compile "com.mysema.querydsl:querydsl-sql:" + queryDslVersion
compile "org.aspectj:aspectjrt:" + aspectjVersion
compile "org.aspectj:aspectjweaver:" + aspectjVersion
compile 'javax:javaee-api:7.0'
//providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile "org.springframework:spring-test:" + springframeworkVersion
}
sourceSets {
generated {
java {
srcDirs = ['src/main/generated']
}
}
}
task generateQueryDSL(type: JavaCompile, group: 'build') {
source = sourceSets.main.java
classpath = configurations.compile
options.compilerArgs = [
"-proc:only",
"-processor", "com.mysema.query.apt.jpa.JPAAnnotationProcessor"
]
destinationDir = sourceSets.generated.java.srcDirs.iterator().next()
}
compileJava {
dependsOn generateQueryDSL
source generateQueryDSL.destinationDir
}
compileGeneratedJava {
dependsOn generateQueryDSL
options.warnings = false
classpath += sourceSets.main.runtimeClasspath
}
clean {
delete sourceSets.generated.java.srcDirs
}
idea {
module {
sourceDirs += file('src/main/generated')
}
}
[Config AppConfig.java]
package pe.kr.ddakker.jpa;
import org.h2.tools.Server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.SQLException;
import java.util.Properties;
/**
* Created by ddakker on 2015-04-09.
*/
@Configuration
@ComponentScan("pe.kr.ddakker.jpa")
@EnableTransactionManagement
@EnableSpringConfigured
@EnableJpaRepositories("pe.kr.ddakker.jpa")
public class AppConfig {
@Autowired
Environment env;
@Bean
public DataSource testDataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
@Bean
public HibernateJpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws SQLException, PropertyVetoException{
LocalContainerEntityManagerFactoryBean emf =
new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(testDataSource());
emf.setJpaVendorAdapter(jpaVendorAdapter());
emf.setPersistenceUnitName("rss");
emf.setPackagesToScan("pe.kr.ddakker.jpa.domain");
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.hbm2ddl.auto", "create-drop");
// properties.put("hibernate.show_sql","true");
// properties.put("hibernate.format_sql", "true");
emf.setJpaProperties(properties);
return emf;
}
@Bean
public JpaTransactionManager transactionManager() throws PropertyVetoException, SQLException{
JpaTransactionManager trans = new JpaTransactionManager();
trans.setEntityManagerFactory(entityManagerFactory().getObject());
return trans;
}
}
[Domain User.java]
package pe.kr.ddakker.jpa.domain;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
public class User implements Serializable {
@Id
@Column(name = "USER_ID")
private Long id;
@Column(name = "USER_NM")
private String name;
@Column(name = "USER_AGE")
private Integer age;
@Temporal(TemporalType.DATE)
@Column(name = "REG_DT")
private Date regDt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getRegDt() {
return regDt;
}
public void setRegDt(Date regDt) {
this.regDt = regDt;
}
}
[Repository UserRepository.java]
package pe.kr.ddakker.jpa.repository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import pe.kr.ddakker.jpa.domain.User;
import java.util.List;
/**
* Created by ddakker on 2015-04-09.
*/
public interface UserRepository extends CrudRepository, QueryDslPredicateExecutor {
public List findByName(String name);
}
[Test UserRepositoryTest.java]
package pe.kr.ddakker.jpa.repository;
import com.mysema.query.jpa.impl.JPAQuery;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import pe.kr.ddakker.jpa.AppConfig;
import pe.kr.ddakker.jpa.domain.QUser;
import pe.kr.ddakker.jpa.domain.User;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* Created by ddakker on 2015-04-09.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class})
@Transactional
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class UserRepositoryTest {
@Autowired private UserRepository userRepository;
@PersistenceContext private EntityManager entityManager;
/**
* Spring JPA CRUD 기능을 이용한다면
*/
@Before
public void create() {
User inputUser = new User();
inputUser.setId(System.currentTimeMillis());
inputUser.setName("ddakker");
inputUser.setAge(33);
inputUser.setRegDt(new Date());
userRepository.save(inputUser);
inputUser = new User();
inputUser.setId(System.currentTimeMillis());
inputUser.setName("petitjjang");
inputUser.setAge(33);
inputUser.setRegDt(new Date());
userRepository.save(inputUser);
inputUser = new User();
inputUser.setId(System.currentTimeMillis());
inputUser.setName("sisigi");
inputUser.setAge(1);
inputUser.setRegDt(new Date());
userRepository.save(inputUser);
assertEquals("전체 사이즈", 3, userRepository.count());
}
/**
* Method Name Query를 사용한다면
*/
@Test
public void testRepository_methodNameQuery() {
List userList = userRepository.findByName("ddakker");
assertEquals("갯수는", 1, userList.size());
}
/**
* Spring Data JPA & QueryDSL Predicate 사용한다면
*/
@Test
public void testRepository_Predicate() {
String name = "dda%";
int age = 33;
QUser user = QUser.user;
Page page = userRepository.findAll(user.name.like(name).and(user.age.eq(age)), new PageRequest(0,10));
assertEquals("검색 결과", 1, page.getNumberOfElements());
Iterable users = userRepository.findAll(user.age.eq(age));
for (User u : users) {
System.out.println("iterable user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt());
}
}
/**
* Spring Data JPA & QueryDSL 확장 기능을 이용한다면
* @throws Exception
*/
@Test
public void testRepository_support() throws Exception {
UserRepositorySupport userRepositorySupport = new UserRepositorySupport(User.class);
userRepositorySupport.setEntityManager(entityManager);
assertEquals("큰 나이", 33, userRepositorySupport.getMaxAge());
assertEquals("작은 나이", 1, userRepositorySupport.getMinAge());
}
/**
* 직접 QueryDSL을 써본다면..
* - 이것을 어느 영역에 둬야 할까...
*/
@Test
public void test_Dsl() {
JPAQuery query = new JPAQuery(entityManager);
QUser qUser = QUser.user;
List userList = query.from(qUser).where(qUser.name.eq("ddakker")).list(qUser);
for (User u : userList) {
System.out.println("list user: " + u.getId() + ", " + u.getName() + ", " + u.getAge() + "," + u.getRegDt());
}
}
}
[Repository UserRepositorySupport.java]
package pe.kr.ddakker.jpa.repository;
import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport;
import pe.kr.ddakker.jpa.domain.QUser;
import pe.kr.ddakker.jpa.domain.User;
/**
* Created by ddakker on 2015-04-09.
*/
public class UserRepositorySupport extends QueryDslRepositorySupport {
public UserRepositorySupport(Class user) {
super(user);
}
public int getMinAge() {
QUser qUser = QUser.user;
return from(qUser).uniqueResult(qUser.age.min());
}
public int getMaxAge() {
QUser qUser = QUser.user;
return from(qUser).uniqueResult(qUser.age.max());
}
}
[참고] https://www.youtube.com/watch?v=ho0fQt8v_HA