hibernate 가 여러 DB를 동시에 만족 시킬 수 있음을 강조하고, 솔루션을 만들 때 여러 DB에 대해 만족할 수 있도록 테스트를 쉽게 하기 위해 간단하게나마 hibernate 설정을 spring 의 @Configuration을 이용하여 제작해 보았습니다.
우선 모든 DB에 대해 공통적으로 적용되는 부분에 대해 다음과 같이 정의했습니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.data.hibernate.springconfiguration; | |
import kr.debop4j.core.tools.StringTool; | |
import kr.debop4j.data.hibernate.forTesting.UnitOfWorkTestContextBase; | |
import kr.debop4j.data.hibernate.interceptor.MultiInterceptor; | |
import kr.debop4j.data.hibernate.interceptor.StatefulEntityInterceptor; | |
import kr.debop4j.data.hibernate.interceptor.UpdateTimestampedInterceptor; | |
import kr.debop4j.data.hibernate.repository.HibernateRepositoryFactory; | |
import kr.debop4j.data.hibernate.unitofwork.UnitOfWorkFactory; | |
import kr.debop4j.data.jdbc.JdbcTool; | |
import lombok.Getter; | |
import lombok.Setter; | |
import lombok.extern.slf4j.Slf4j; | |
import org.hibernate.ConnectionReleaseMode; | |
import org.hibernate.SessionFactory; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactoryBean; | |
import org.springframework.orm.hibernate4.HibernateTransactionManager; | |
import org.springframework.orm.hibernate4.LocalSessionFactoryBean; | |
import javax.sql.DataSource; | |
import java.io.IOException; | |
import java.util.Properties; | |
/** | |
* hibernate 의 환경설정을 spring framework의 bean 환경설정으로 구현했습니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 21. | |
*/ | |
@Slf4j | |
public abstract class HibernateConfigBase { | |
@Getter | |
@Setter | |
private UnitOfWorkTestContextBase testContext; | |
abstract protected String getDatabaseName(); | |
abstract protected String[] getMappedPackageNames(); | |
protected Properties hibernateProperties() { | |
Properties props = new Properties(); | |
props.put(Environment.FORMAT_SQL, "true"); | |
props.put(Environment.HBM2DDL_AUTO, "create"); // create | spawn | spawn-drop | update | validate | |
props.put(Environment.SHOW_SQL, "true"); | |
props.put(Environment.RELEASE_CONNECTIONS, ConnectionReleaseMode.ON_CLOSE); | |
props.put(Environment.AUTOCOMMIT, "true"); | |
props.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread"); | |
props.put(Environment.STATEMENT_BATCH_SIZE, "50"); | |
return props; | |
} | |
protected DataSource buildDataSource(String driverClass, String url, String username, String password) { | |
return JdbcTool.getDataSource(driverClass, url, username, password); | |
} | |
protected DataSource buildEmbeddedDataSource() { | |
EmbeddedDatabaseFactoryBean bean = new EmbeddedDatabaseFactoryBean(); | |
bean.afterPropertiesSet(); | |
return bean.getObject(); | |
} | |
@Bean(destroyMethod = "close") | |
abstract public DataSource dataSource(); | |
/** | |
* factoryBean 에 추가 설정을 지정할 수 있습니다. | |
*/ | |
protected void setupSessionFactory(LocalSessionFactoryBean factoryBean) { } | |
@Bean | |
public SessionFactory sessionFactory() { | |
if (log.isInfoEnabled()) | |
log.info("SessionFactory Bean을 생성합니다..."); | |
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); | |
String[] packageNames = getMappedPackageNames(); | |
if (packageNames != null) { | |
log.info("hibernate용 entity를 scan합니다. packages=[{}]", StringTool.listToString(packageNames)); | |
factoryBean.setPackagesToScan(packageNames); | |
} | |
factoryBean.setHibernateProperties(hibernateProperties()); | |
factoryBean.setDataSource(dataSource()); | |
factoryBean.setEntityInterceptor(hibernateInterceptor()); | |
// Drived class에서 추가 작업을 수행할 수 있도록 합니다. | |
setupSessionFactory(factoryBean); | |
try { | |
factoryBean.afterPropertiesSet(); | |
if (log.isInfoEnabled()) | |
log.info("SessionFactory Bean을 생성했습니다!!!"); | |
return factoryBean.getObject(); | |
} catch (IOException e) { | |
throw new RuntimeException("SessionFactory 빌드에 실패했습니다.", e); | |
} | |
} | |
@Bean | |
public HibernateTransactionManager transactionManager() { | |
return new HibernateTransactionManager(sessionFactory()); | |
} | |
@Bean | |
public MultiInterceptor hibernateInterceptor() { | |
MultiInterceptor interceptor = new MultiInterceptor(); | |
interceptor.getInterceptors().add(statuefulEntityInterceptor()); | |
interceptor.getInterceptors().add(updateTimestampedInterceptor()); | |
return interceptor; | |
} | |
@Bean | |
public StatefulEntityInterceptor statuefulEntityInterceptor() { | |
return new StatefulEntityInterceptor(); | |
} | |
@Bean | |
public UpdateTimestampedInterceptor updateTimestampedInterceptor() { | |
return new UpdateTimestampedInterceptor(); | |
} | |
@Bean | |
public UnitOfWorkFactory unitOfWorkFactory() { | |
UnitOfWorkFactory factory = new UnitOfWorkFactory(); | |
factory.setSessionFactory(sessionFactory()); | |
return factory; | |
} | |
@Bean | |
public HibernateRepositoryFactory hibernateRepositoryFactory() { | |
return new HibernateRepositoryFactory(); | |
} | |
} | |
public SessionFactory sessionFactory() {...} 함수가 가장 중요하고, 나머지는 뭐 별로...
당연히 hibernate 설정 클래스이니까... ㅎㅎ
sessionFactory를 만드는데, 여러가지 필요한 설정들을 지정하게 하는데, 미리 정의된 부분도 있고, 사용자가 더 필요한 부분은 "setupSessionFactory(factoryBean)" 위임 메소드를 이용하여, 추가로 정의 할 수 있도록 했습니다.
그럼 대표적으로 사용하는 DB인 HSQL, MySQL, PostgreSQL 에 대한 기본 환경설정 클래스를 보겠습니다.
1. HSql 메모리 DB 사용 (단순 테스트시 유용)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.data.hibernate.springconfiguration; | |
import kr.debop4j.data.hibernate.forTesting.DatabaseEngine; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Bean; | |
import javax.sql.DataSource; | |
import java.util.Properties; | |
/** | |
* HSql 을 DB로 사용하는 Hibernate Configuration | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 21. | |
*/ | |
public abstract class HSqlConfigBase extends HibernateConfigBase { | |
public DatabaseEngine getDatabaseEngine() { | |
return DatabaseEngine.HSql; | |
} | |
public String getDatabaseName() { | |
return "mem"; | |
} | |
@Bean(destroyMethod = "close") | |
public DataSource dataSource() { | |
return buildDataSource("org.hsqldb.jdbcDriver", | |
"jdbc:hsqldb:" + getDatabaseName() + ":test", | |
"sa", | |
""); | |
} | |
protected Properties hibernateProperties() { | |
Properties props = super.hibernateProperties(); | |
props.put(Environment.DIALECT, "org.hibernate.dialect.HSQLDialect"); | |
return props; | |
} | |
} |
2. MySQL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.data.hibernate.springconfiguration; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import javax.sql.DataSource; | |
import java.util.Properties; | |
/** | |
* kr.debop4j.data.hibernate.springconfiguration.MySqlDbConfiguration | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 21. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public abstract class MySqlConfigBase extends HibernateConfigBase { | |
@Override | |
public String getDatabaseName() { | |
return "hibernate"; | |
} | |
@Bean(destroyMethod = "close") | |
public DataSource dataSource() { | |
return buildDataSource("com.mysql.jdbc.Driver", | |
"jdbc:mysql://localhost/" + getDatabaseName(), | |
"root", | |
"root"); | |
} | |
@Override | |
@Bean | |
public Properties hibernateProperties() { | |
Properties props = super.hibernateProperties(); | |
props.put(Environment.DIALECT, "org.hibernate.dialect.MySQL5InnoDBDialect"); | |
return props; | |
} | |
} |
3. PostgreSql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.data.hibernate.springconfiguration; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import javax.sql.DataSource; | |
import java.util.Properties; | |
/** | |
* PostgreSQL DB를 사용하는 Hibernate 용 환경설정입니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 21. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public abstract class PostgreSqlConfigBase extends HibernateConfigBase { | |
@Override | |
public String getDatabaseName() { | |
return "hibernate"; | |
} | |
@Bean(destroyMethod = "close") | |
public DataSource dataSource() { | |
return buildDataSource("org.postgresql.Driver", | |
"jdbc:postgresql://localhost/" + getDatabaseName() + "?Set=UTF8", | |
"root", | |
"root"); | |
} | |
@Bean | |
public Properties hibernateProperties() { | |
Properties props = super.hibernateProperties(); | |
props.put(Environment.DIALECT, "org.hibernate.dialect.PostgreSQL82Dialect"); | |
return props; | |
} | |
} |
4. PostgreSql with pgpool-II
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.data.hibernate.springconfiguration; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import javax.sql.DataSource; | |
/** | |
* PostgreSQL 용 ConnectionPool과 Replication을 제공하는 PgPool 로 Connection을 만듭니다. (포트 9999를 사용합니다) | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 2. 26. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public abstract class PgPoolConfigBase extends PostgreSqlConfigBase { | |
@Bean(destroyMethod = "close") | |
public DataSource dataSource() { | |
return buildDataSource("org.postgresql.Driver", | |
"jdbc:postgresql://localhost:9999/" + getDatabaseName() + "?Set=UTF8", | |
"root", | |
"root"); | |
} | |
} |
과 같다.
아주 사소한 port 번호 같은 것은 기본 값을 사용했다.
뭐 각각 다른 것은 DB명, ConnectionString, Dialect 등이다. 별 차이 없지만, 이렇게 구조적으로 상속체계를 해 놓으면, 실전에서 아주 편리하다.
실제 사용하는 예는 HSql 과 PostgreSql 의 예를 들어보자.
1. HSql 메모리 DB를 이용한 환경 설정 (2nd 캐시도 적용됨)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.access; | |
import kr.debop4j.access.model.organization.Company; | |
import kr.debop4j.access.model.product.Product; | |
import kr.debop4j.access.model.workcalendar.WorkCalendar; | |
import kr.debop4j.data.hibernate.springconfiguration.HSqlConfigBase; | |
import org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import java.util.Properties; | |
/** | |
* HSql 메모리 DB를 사용하는 Hibernate 환경 설정입니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 3. 7. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public class UsingHSqlConfiguration extends HSqlConfigBase { | |
@Override | |
protected String[] getMappedPackageNames() { | |
return new String[]{ | |
Company.class.getPackage().getName(), | |
Product.class.getPackage().getName(), | |
WorkCalendar.class.getPackage().getName(), | |
}; | |
} | |
@Override | |
public Properties hibernateProperties() { | |
Properties props = super.hibernateProperties(); | |
props.put(Environment.USE_SECOND_LEVEL_CACHE, true); | |
props.put(Environment.USE_QUERY_CACHE, true); | |
props.put(Environment.CACHE_REGION_PREFIX, "debop4j-access"); | |
props.put(Environment.CACHE_REGION_FACTORY, SingletonEhCacheRegionFactory.class.getName()); | |
props.put(Environment.CACHE_PROVIDER_CONFIG, "classpath:ehcache.xml"); | |
return props; | |
} | |
} |
2. PostgreSql 의 "HAccess" DB 를 이용한 환경 설정 (2nd Cache도 적용됨)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.access; | |
import kr.debop4j.access.model.organization.Company; | |
import kr.debop4j.access.model.organization.CompanyCode; | |
import kr.debop4j.access.model.product.Product; | |
import kr.debop4j.access.model.workcalendar.WorkCalendar; | |
import kr.debop4j.data.hibernate.springconfiguration.PostgreSqlConfigBase; | |
import org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory; | |
import org.hibernate.cfg.Environment; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
import java.util.Properties; | |
/** | |
* PostgreSQL DB를 사용하는 Hibernate 환경설정입니다. | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 3. 7. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
public class UsingPostgreSqlConfiguration extends PostgreSqlConfigBase { | |
@Override | |
public String getDatabaseName() { | |
return "HAccess"; | |
} | |
@Override | |
protected String[] getMappedPackageNames() { | |
return new String[]{ | |
CompanyCode.class.getPackage().getName(), | |
Company.class.getPackage().getName(), | |
Product.class.getPackage().getName(), | |
WorkCalendar.class.getPackage().getName(), | |
}; | |
} | |
@Override | |
public Properties hibernateProperties() { | |
Properties props = super.hibernateProperties(); | |
props.put(Environment.USE_SECOND_LEVEL_CACHE, true); | |
props.put(Environment.USE_QUERY_CACHE, true); | |
props.put(Environment.CACHE_REGION_FACTORY, SingletonEhCacheRegionFactory.class.getName()); | |
props.put(Environment.CACHE_PROVIDER_CONFIG, "classpath:ehcache.xml"); | |
return props; | |
} | |
} |
이 것으로 끝나는게 아니고, Application 용 환경설정에서는 다음과 같이 위의 두 가지 환경설정 Class 를 입맛에 따라 Import 해서 테스트를 수행하면 된다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package kr.debop4j.access; | |
import org.springframework.context.annotation.ComponentScan; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.Import; | |
import org.springframework.transaction.annotation.EnableTransactionManagement; | |
/** | |
* kr.debop4j.access.AppConfig | |
* User: sunghyouk.bae@gmail.com | |
* Date: 13. 3. 2. | |
*/ | |
@Configuration | |
@EnableTransactionManagement | |
@ComponentScan({"kr.debop4j.access.repository", "kr.debop4j.access.service"}) | |
@Import({UsingPostgreSqlConfiguration.class}) | |
public class AppConfig { | |
// @ComponentScan 으로 @Repository, @Service 는 정의할 필요 없다. | |
} |
정말 쉽죠?
이제 실제 단위 테스트 클래스에서 AppConfig를 지정해서 테스트를 수행하시면 됩니다^^
ORM의 장점 중에 여러 DB에 대해 일관된 작업이 가능하므로, 솔루션을 만들때는 최선의 선택이라 생각하는지라, 위와 같은 설정으로 개발을 수행합니다. (비록 한가지 DB만을 대상으로 하는 시스템이라도... 향후 어떻게 될지 모르고, 코드 재활용성도 생각하고^^)
댓글 없음:
댓글 쓰기