2013년 3월 24일 일요일

hibernate-ogm configuration for spring framework

요즘 hibernate-ogm 을 제품에 적용하기 위해 공부하고 있습니다만, 아직 많은 활용이 안되나 봅니다. 개발 자료가 별로 없어, 소스와 테스트 코드를 분석하면서 공부하고 있습니다만...

역시 제 나름대로 테스트 환경부터 만들어서 공부하는 습관이 도움이 되네요...
그래서 제가 만든 환경에 대해 설명하고, 이 것을 바탕으로 hibernate-ogm을 제품에 쉽게 적용할 수 있도록 해 보겠습니다.

hibernate-ogm 도 hibernate 와 유사하게 환경설정을 합니다. 다만, hibernate 3.x 대의 Configuration을 이용하여 SessionFactory를 build 하는 것과 유사하게 SessionFactory를 빌드합니다. 즉 hibernate 4.x대와는 다르게 설정하네요.

또 한가지 확실하지는 않지만,  Configuration에서 Package 추가는 잘 안되고, annotationClass 는 제대로 되는군요...
=> 제가 잘못알고 알고 있었네요. package 추가 시에는 scan을 해줘야 하는데 그것은 새로 구현을 해야 하는 거더군요^^

우선 모든 Datastore (NoSql이라 그런지 Database 라 하지 않고 Datastore 라고 하는군요) 에 공통되는 부분을 구현한 GridDatastoreConfigBase.java 파일을 보면


package kr.debop4j.ogm.spring.cfg;
import kr.debop4j.data.hibernate.interceptor.StatefulEntityInterceptor;
import kr.debop4j.data.hibernate.repository.HibernateRepositoryFactory;
import kr.debop4j.data.hibernate.unitofwork.UnitOfWorkFactory;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.ogm.cfg.OgmConfiguration;
import org.hibernate.ogm.datastore.impl.DatastoreServices;
import org.hibernate.ogm.datastore.spi.DatastoreProvider;
import org.hibernate.ogm.dialect.GridDialect;
import org.hibernate.service.Service;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* hibernate-ogm 의 환경설정을 Spring Configuration으로 구현합니다.
*
* @author sunghyouk.bae@gmail.com
* 13. 3. 23. 오후 2:22
*/
@Configuration
@Slf4j
public abstract class GridDatastoreConfigBase {
/**
* DataStoreProvider 를 제공합니다.
*/
@Bean
public DatastoreProvider datastoreProvider() {
return (DatastoreProvider) getService(DatastoreProvider.class);
}
/**
* {@link GridDialect} 를 제공합니다.
*/
@Bean
public GridDialect gridDialect() {
return ((DatastoreServices) getService(DatastoreServices.class)).getGridDialect();
}
/**
* Hibernate SessionFactory 를 제공합니다.
*/
@Bean
public SessionFactory sessionFactory() {
if (log.isInfoEnabled())
log.info("hiberante-ogm 용 SessionFactory를 생성합니다...");
OgmConfiguration cfg = new OgmConfiguration();
for (String pkgName : getMappedPackageNames()) {
cfg.addPackage(pkgName);
}
for (Class annoatatedClass : getMappedEntities()) {
cfg.addAnnotatedClass(annoatatedClass);
}
cfg.setInterceptor(hibernateInterceptor());
cfg.setProperties(getHibernateOgmProperties());
if (log.isInfoEnabled())
log.info("hiberante-ogm 용 SessionFactory를 생성했습니다!!!");
return cfg.buildSessionFactory();
}
@Bean
public org.hibernate.Interceptor hibernateInterceptor() {
return new StatefulEntityInterceptor();
}
@Bean
public UnitOfWorkFactory unitOfWorkFactory() {
if (log.isInfoEnabled())
log.info("UnitOfWorkFactory를 생성합니다...");
UnitOfWorkFactory factory = new UnitOfWorkFactory();
factory.setSessionFactory(sessionFactory());
return factory;
}
@Bean
public HibernateRepositoryFactory hibernateRepositoryFactory() {
return new HibernateRepositoryFactory();
}
protected String getDatabaseName() {
return "debop4j_ogm_test";
}
protected String[] getMappedPackageNames() {
return new String[0];
}
protected Class[] getMappedEntities() {
return new Class[0];
}
protected Properties getHibernateProperties() {
Properties props = new Properties();
props.setProperty(Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true");
props.setProperty(Environment.HBM2DDL_AUTO, "none");
return props;
}
protected Properties getHibernateOgmProperties() {
return getHibernateProperties();
}
protected org.hibernate.service.Service getService(Class<? extends Service> serviceImpl) {
SessionFactoryImplementor sessionFactory = sfi();
ServiceRegistryImplementor serviceRegistry = sessionFactory.getServiceRegistry();
return serviceRegistry.getService(serviceImpl);
}
protected SessionFactoryImplementor sfi() {
return (SessionFactoryImplementor) sessionFactory();
}
}

public SessionFactory sessionFactory() {...} 는 아주 익숙한 코드지요? 단지 Configuration class가 hibernate 것이 아닌 hibernate-ogm의 OgmConfiguration을 사용한다는 것만 다릅니다.

아래의 getDatabaseName(), getMappedPackageNames(), getMappedEntities() 메소드는 각자 Datastore와 제품에 따라 재정의하면 됩니다.
그 밑에 getHibernateProperties() 와 getHibernateOgmProperties() 는 각각 Hibernate 설정과 Hibernate-Ogm 설정을 추가할 수 있습니다. 이 부분도 재정의를 통해 추가하시면 됩니다.

그럼 MongoDB를 사용하는 환경설정은? 위의 GridDataStoreConfigBase를 상속하여 몇가지 메소드를 재정의만 하면 됩니다.

package kr.debop4j.ogm.spring.cfg.mongodb;
import kr.debop4j.ogm.spring.cfg.GridDatastoreConfigBase;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.ogm.datastore.mongodb.AssociationStorage;
import org.hibernate.ogm.datastore.mongodb.Environment;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* MongoDB를 Datastore로 사용하는 hibernate-ogm 에 대한 환경설정입니다.
*
* @author sunghyouk.bae@gmail.com
* 13. 3. 23. 오후 3:53
*/
@Configuration
@Slf4j
public abstract class MongoDBConfigBase extends GridDatastoreConfigBase {
public static final String MONGODB_DATASTORE_PROVIDER = "org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider";
protected Properties getHibernateOgmProperties() {
Properties props = getHibernateProperties();
props.setProperty("hibernate.ogm.datastore.provider", MONGODB_DATASTORE_PROVIDER);
props.put(Environment.MONGODB_DATABASE, getDatabaseName());
props.put(Environment.MONGODB_TIMEOUT, 200);
props.put(Environment.MONGODB_ASSOCIATIONS_STORE, getAssociationStorage().name());
return props;
}
protected AssociationStorage getAssociationStorage() {
return AssociationStorage.COLLECTION;
}
@Bean
public javax.transaction.TransactionManager transactionManager() {
return com.arjuna.ats.jta.TransactionManager.transactionManager();
}
}

MongoDB 만의 설정을 보면 database 명을 설정해줘야 하고, 엔티티의 저장 방식을 설정해주게
됩니다. AssociationStorage enum 값의 설명을 보면 ...

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* JBoss, Home of Professional Open Source
* Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.hibernate.ogm.datastore.mongodb;
/**
* Defines the various association storage strategies
*
* @author Alan Fitton <alan at eth0.org.uk>
* @author Emmanuel Bernard <emmanuel@hibernate.org>
*/
public enum AssociationStorage {
/**
* Store the association info in a unique MongoDB collection for all associations
*/
GLOBAL_COLLECTION,
/**
* Store the association in a dedicated MongoDB collection per association
*/
COLLECTION,
/**
* Store association information from within the entity
*/
IN_ENTITY
}

입니다. Entity 들을 전역 컬렉션에 저장, 지정한 컬렉션에 저장, 엔티티별로 저장과 같이 3가지 방식이 있습니다.

뭐 Lazy Initialization을 많이 사용하려면 엔티티별로 저장하고, 일반적으로는 컬렉션에 저장하는게 좋으리라 생각됩니다.

자 그럼 실제 테스트 시에 사용할 Configuration을 보면은

package org.hibernate.ogm.test.mongodb;
import kr.debop4j.ogm.spring.cfg.mongodb.MongoDBConfigBase;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.ogm.test.mongodb.model.Module;
import org.hibernate.ogm.test.mongodb.model.Project;
import org.springframework.context.annotation.Configuration;
/**
* MongoDB 를 DataStore로 사용하는 Configuration
*
* @author sunghyouk.bae@gmail.com
* 13. 3. 23. 오후 2:31
*/
@Configuration
@Slf4j
public class MongoDBConfiguration extends MongoDBConfigBase {
@Override
protected String getDatabaseName() {
return "debop4j_ogm_test";
}
@Override
protected String[] getMappedPackageNames() {
return new String[]{
Project.class.getPackage().getName(),
};
}
@Override
protected Class[] getMappedEntities() {
return new Class[]{
Module.class,
Project.class
};
}
}

과 같습니다. DB명을 지정하고, 매핑된 엔티티를 지정해 주는 것으로 끝납니다^^
이제부터는 위의 Configuration을 이용하여 테스트 코드를 작성하여 테스트를 수행하면됩니다

hibernate-ogm은 현재까지 NoSQL을 단순 저장소로 밖에 활용 못한 것을 Object Grid 방식으로 사용할 수 있도록 한 차원 업그래이드된 방법을 제공합니다.
이를 통해 앞으로는 RDBMS 뿐 아니라 NoSQL도 특정 제품에 구애받지 않고 사용할 수 있었으면 합니다.



댓글 없음: