최근 인터넷 곳곳에서 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() { ListuserList = 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(Classuser) { 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