您的位置:首页 > 编程语言 > Java开发

Spring3 整合Hibernate3.5 动态切换SessionFactory (切换数据库方言)

2015-08-25 23:54 411 查看
一、缘由

上一篇文章Spring3.3整合Hibernate3、MyBatis3.2
配置多数据源/动态切换数据源方法介绍到了怎么样在Sping、MyBatis、Hibernate整合的应用中动态切换DataSource数据源的方法,但最终遗留下一个问题:不能切换数据库方言。数据库方言可能在当前应用的架构中意义不是很大,但是如果单纯用MyBatis或Hibernate做数据库持久化操作,还是要处理这一问题。

那么下面将介绍怎么样动态切换SessionFactory,为什么要切换SessionFactory?
因为这里切换SessionFactory就可以实现多数据源和多个SessionFactory,每个SessionFactory有自己独立的数据库配置和SessionFactory的相关配置。我们的数据库方言就配置在SessionFactory这里,所以实现了切换SessionFactory也就实现了切换数据库方言的问题。这个主要是针对Hibernate来操作的,而MyBatis则需要动态切换SqlSessionFactory才行。

二、实现代码

1、定义全局切换SessionFactory的工具

packagecom.hoo.framework.spring.support;


/**

*<b>function:</b>多数据源

*@authorhoojo

*@createDate2013-9-27上午11:36:57

*@fileCustomerContextHolder.java

*@packagecom.hoo.framework.spring.support

*@projectSHMB

*@blog'target='_blank'>http://blog.csdn.net/IBM_hoojo[/code]
*@emailhoojo_@126.com

*@version1.0

*/

publicabstractclassCustomerContextHolder{


publicfinalstaticStringSESSION_FACTORY_MYSQL="mysql";

publicfinalstaticStringSESSION_FACTORY_ORACLE="oracle";


privatestaticfinalThreadLocal<String>contextHolder=newThreadLocal<String>();


publicstaticvoidsetCustomerType(StringcustomerType){

contextHolder.set(customerType);

}


publicstaticStringgetCustomerType(){

returncontextHolder.get();

}


publicstaticvoidclearCustomerType(){

contextHolder.remove();

}

}


同样上面的静态变量和前一文章中介绍的一致,它需要和下面配置文件中的SessionFactory的key对应。

2、实现自己的SessionFactory
定义好接口

packagecom.hoo.framework.spring.support.core;


importorg.hibernate.SessionFactory;


/**

*<b>function:</b>动态SessionFactory接口

*@authorhoojo

*@createDate2013-10-12下午03:29:52

*@fileDynamicSessionFactory.java

*@packagecom.hoo.framework.spring.support.core

*@projectSHMB

*@blog'target='_blank'>http://blog.csdn.net/IBM_hoojo[/code]
*@emailhoojo_@126.com

*@version1.0

*/

publicinterfaceDynamicSessionFactoryextendsSessionFactory{


publicSessionFactorygetHibernateSessionFactory();

}


实现接口

packagecom.hoo.framework.spring.support.core;


importjava.io.Serializable;

importjava.sql.Connection;

importjava.util.Map;

importjava.util.Set;

importjavax.naming.NamingException;

importjavax.naming.Reference;

importorg.hibernate.Cache;

importorg.hibernate.HibernateException;

importorg.hibernate.Interceptor;

importorg.hibernate.SessionFactory;

importorg.hibernate.StatelessSession;

importorg.hibernate.TypeHelper;

importorg.hibernate.classic.Session;

importorg.hibernate.engine.FilterDefinition;

importorg.hibernate.metadata.ClassMetadata;

importorg.hibernate.metadata.CollectionMetadata;

importorg.hibernate.stat.Statistics;

importcom.hoo.framework.spring.support.CustomerContextHolder;


/**

*<b>function:</b>动态数据源实现

*@authorhoojo

*@createDate2013-10-12下午03:31:31

*@fileDynamicSessionFactoryImpl.java

*@packagecom.hoo.framework.spring.support.core

*@projectSHMB

*@blog'target='_blank'>http://blog.csdn.net/IBM_hoojo[/code]
*@emailhoojo_@126.com

*@version1.0

*/

@SuppressWarnings({"unchecked","deprecation"})

publicclassDynamicSessionFactoryImplimplementsDynamicSessionFactory{


privatestaticfinallongserialVersionUID=5384069312247414885L;


privateMap<Object,SessionFactory>targetSessionFactorys;

privateSessionFactorydefaultTargetSessionFactory;


/**

*@seecom.hoo.framework.spring.support.core.DynamicSessionFactory#getHibernateSessionFactory()

*<b>function:</b>重写这个方法,这里最关键

*@authorhoojo

*@createDate2013-10-18上午10:45:25

*/

@Override

publicSessionFactorygetHibernateSessionFactory(){

SessionFactorytargetSessionFactory=targetSessionFactorys.get(CustomerContextHolder.getCustomerType());

if(targetSessionFactory!=null){

returntargetSessionFactory;

}elseif(defaultTargetSessionFactory!=null){

returndefaultTargetSessionFactory;

}

returnnull;

}



@Override

publicvoidclose()throwsHibernateException{

this.getHibernateSessionFactory().close();

}


@Override

publicbooleancontainsFetchProfileDefinition(Strings){

returnthis.getHibernateSessionFactory().containsFetchProfileDefinition(s);

}


@Override

publicvoidevict(Classclazz)throwsHibernateException{

this.getHibernateSessionFactory().evict(clazz);

}


@Override

publicvoidevict(Classclazz,Serializableserializable)throwsHibernateException{

this.getHibernateSessionFactory().evict(clazz,serializable);

}


@Override

publicvoidevictCollection(Strings)throwsHibernateException{

this.getHibernateSessionFactory().evictCollection(s);

}


@Override

publicvoidevictCollection(Strings,Serializableserializable)throwsHibernateException{

this.getHibernateSessionFactory().evictCollection(s,serializable);

}


@Override

publicvoidevictEntity(Stringentity)throwsHibernateException{

this.getHibernateSessionFactory().evictEntity(entity);

}


@Override

publicvoidevictEntity(Stringentity,Serializableserializable)throwsHibernateException{

this.getHibernateSessionFactory().evictEntity(entity,serializable);

}


@Override

publicvoidevictQueries()throwsHibernateException{

this.getHibernateSessionFactory().evictQueries();

}


@Override

publicvoidevictQueries(Stringqueries)throwsHibernateException{

this.getHibernateSessionFactory().evictQueries(queries);

}


@Override

publicMap<String,ClassMetadata>getAllClassMetadata(){

returnthis.getHibernateSessionFactory().getAllClassMetadata();

}


@Override

publicMapgetAllCollectionMetadata(){

returnthis.getHibernateSessionFactory().getAllClassMetadata();

}


@Override

publicCachegetCache(){

returnthis.getHibernateSessionFactory().getCache();

}


@Override

publicClassMetadatagetClassMetadata(Classclazz){

returnthis.getHibernateSessionFactory().getClassMetadata(clazz);

}


@Override

publicClassMetadatagetClassMetadata(StringclassMetadata){

returnthis.getHibernateSessionFactory().getClassMetadata(classMetadata);

}


@Override

publicCollectionMetadatagetCollectionMetadata(StringcollectionMetadata){

returnthis.getHibernateSessionFactory().getCollectionMetadata(collectionMetadata);

}


@Override

publicSessiongetCurrentSession()throwsHibernateException{

returnthis.getHibernateSessionFactory().getCurrentSession();

}


@Override

publicSetgetDefinedFilterNames(){

returnthis.getHibernateSessionFactory().getDefinedFilterNames();

}


@Override

publicFilterDefinitiongetFilterDefinition(Stringdefinition)throwsHibernateException{

returnthis.getHibernateSessionFactory().getFilterDefinition(definition);

}


@Override

publicStatisticsgetStatistics(){

returnthis.getHibernateSessionFactory().getStatistics();

}


@Override

publicTypeHelpergetTypeHelper(){

returnthis.getHibernateSessionFactory().getTypeHelper();

}


@Override

publicbooleanisClosed(){

returnthis.getHibernateSessionFactory().isClosed();

}


@Override

publicSessionopenSession()throwsHibernateException{

returnthis.getHibernateSessionFactory().openSession();

}


@Override

publicSessionopenSession(Interceptorinterceptor)throwsHibernateException{

returnthis.getHibernateSessionFactory().openSession(interceptor);

}


@Override

publicSessionopenSession(Connectionconnection){

returnthis.getHibernateSessionFactory().openSession(connection);

}


@Override

publicSessionopenSession(Connectionconnection,Interceptorinterceptor){

returnthis.getHibernateSessionFactory().openSession(connection,interceptor);

}


@Override

publicStatelessSessionopenStatelessSession(){

returnthis.getHibernateSessionFactory().openStatelessSession();

}


@Override

publicStatelessSessionopenStatelessSession(Connectionconnection){

returnthis.getHibernateSessionFactory().openStatelessSession(connection);

}


@Override

publicReferencegetReference()throwsNamingException{

returnthis.getHibernateSessionFactory().getReference();

}


publicvoidsetTargetSessionFactorys(Map<Object,SessionFactory>targetSessionFactorys){

this.targetSessionFactorys=targetSessionFactorys;

}


publicvoidsetDefaultTargetSessionFactory(SessionFactorydefaultTargetSessionFactory){

this.defaultTargetSessionFactory=defaultTargetSessionFactory;

}


}


上面最重要的就是getHibernateSessionFactory重写这个方法,其他方法和原来实现的无异。重写这个方法后利用CustomerContextHolder动态设置SessionFactory类型就可以动态的切换SessionFactory。

3、动态的事务管理器,因为我们这里是动态切换SessionFactory,所以事务这块也需要动态切换SessionFactory来完成事务的操作。

packagecom.hoo.framework.spring.support.tx;


importjavax.sql.DataSource;

importorg.hibernate.SessionFactory;

importorg.springframework.orm.hibernate3.HibernateTransactionManager;

importorg.springframework.orm.hibernate3.SessionFactoryUtils;

importcom.hoo.framework.spring.support.core.DynamicSessionFactory;


/**

*<b>function:</b>重写HibernateTransactionManager事务管理器,实现自己的动态的事务管理器

*@authorhoojo

*@createDate2013-10-12下午03:54:02

*@fileDynamicTransactionManager.java

*@packagecom.hoo.framework.spring.support.tx

*@projectSHMB

*@blog'target='_blank'>http://blog.csdn.net/IBM_hoojo[/code]
*@emailhoojo_@126.com

*@version1.0

*/

publicclassDynamicTransactionManagerextendsHibernateTransactionManager{


privatestaticfinallongserialVersionUID=-4655721479296819154L;


/**

*@seeorg.springframework.orm.hibernate4.HibernateTransactionManager#getDataSource()

*<b>function:</b>重写

*@authorhoojo

*@createDate2013-10-12下午03:55:24

*/

@Override

publicDataSourcegetDataSource(){

returnSessionFactoryUtils.getDataSource(getSessionFactory());

}


/**

*@seeorg.springframework.orm.hibernate4.HibernateTransactionManager#getSessionFactory()

*<b>function:</b>重写

*@authorhoojo

*@createDate2013-10-12下午03:55:24

*/

@Override

publicSessionFactorygetSessionFactory(){

DynamicSessionFactorydynamicSessionFactory=(DynamicSessionFactory)super.getSessionFactory();

SessionFactoryhibernateSessionFactory=dynamicSessionFactory.getHibernateSessionFactory();

returnhibernateSessionFactory;

}

}


这里主要重写getDataSource()/getSessionFactory()这两个方法,getSessionFactory方法是利用我们上面定义的接口来动态获取我们在上下文(CustomerContextHolder)中定义切换的SessionFactory对象。而getDataSource则是获得动态SessionFactory的DataSource,这里也不难理解。

4、至此,重写的接口和实现都完成,下面开始配置相关的代码

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="http://www.springframework.org/schema/beans"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:tx="http://www.springframework.org/schema/tx"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.2.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.2.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">


<!--配置c3p0数据源-->

<beanid="dataSourceOracle"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">

<propertyname="driverClass"value="${datasource.driver}"/>

<propertyname="jdbcUrl"value="${datasource.url}"/>

<propertyname="user"value="${datasource.username}"/>

<propertyname="password"value="${datasource.password}"/>


<propertyname="acquireIncrement"value="${c3p0.acquireIncrement}"/>

<propertyname="initialPoolSize"value="${c3p0.initialPoolSize}"/>

<propertyname="minPoolSize"value="${c3p0.minPoolSize}"/>

<propertyname="maxPoolSize"value="${c3p0.maxPoolSize}"/>

<propertyname="maxIdleTime"value="${c3p0.maxIdleTime}"/>

<propertyname="idleConnectionTestPeriod"value="${c3p0.idleConnectionTestPeriod}"/>

<propertyname="maxStatements"value="${c3p0.maxStatements}"/>

<propertyname="numHelperThreads"value="${c3p0.numHelperThreads}"/>

<propertyname="preferredTestQuery"value="${c3p0.preferredTestQuery}"/>

<propertyname="testConnectionOnCheckout"value="${c3p0.testConnectionOnCheckout}"/>

</bean>


<beanid="dataSourceMySQL"class="com.mchange.v2.c3p0.ComboPooledDataSource"destroy-method="close">

<propertyname="driverClass"value="com.mysql.jdbc.Driver"/>

<propertyname="jdbcUrl"value="jdbc:mysql://172.31.108.178:3306/world?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull"/>

<propertyname="user"value="root"/>

<propertyname="password"value="jp2011"/>


<propertyname="acquireIncrement"value="${c3p0.acquireIncrement}"/>

<propertyname="initialPoolSize"value="${c3p0.initialPoolSize}"/>

<propertyname="minPoolSize"value="${c3p0.minPoolSize}"/>

<propertyname="maxPoolSize"value="${c3p0.maxPoolSize}"/>

<propertyname="maxIdleTime"value="${c3p0.maxIdleTime}"/>

<propertyname="idleConnectionTestPeriod"value="${c3p0.idleConnectionTestPeriod}"/>

<propertyname="maxStatements"value="${c3p0.maxStatements}"/>

<propertyname="numHelperThreads"value="${c3p0.numHelperThreads}"/>

<propertyname="preferredTestQuery"value="${c3p0.preferredTestQuery}"/>

<propertyname="testConnectionOnCheckout"value="${c3p0.testConnectionOnCheckout}"/>

</bean>


<!--Annotation配置sessionFactory,配置数据库连接,注入hibernate数据库配置-->

<beanid="mySQLSessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceMySQL"/>

<propertyname="packagesToScan"value="com.hoo.**.mysqlentity"/>

<propertyname="annotatedClasses">

<array>

<value>com.hoo.common.entity.IDGenerator</value>

</array>

</property>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>

<!--链接释放策略on_close|after_transaction|after_statement|auto-->

<propkey="hibernate.connection.release_mode">after_transaction</prop>

<propkey="hibernate.show_sql">true</prop>

<propkey="hibernate.format_sql">true</prop>

</props>

</property>

</bean>


<!--Annotation配置sessionFactory,配置数据库连接,注入hibernate数据库配置-->

<beanid="oracleSessionFactory"class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

<propertyname="dataSource"ref="dataSourceOracle"/>

<propertyname="packagesToScan"value="com.hoo.**.entity"/>

<propertyname="hibernateProperties">

<props>

<propkey="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>

<propkey="hibernate.connection.release_mode">after_transaction</prop>

<propkey="hibernate.show_sql">true</prop>

<propkey="hibernate.format_sql">true</prop>

<!--propkey="hibernate.hbm2ddl.auto">update</prop-->

</props>

</property>

</bean>


<!--动态SessionFactory-->

<beanid="sessionFactory"class="com.hoo.framework.spring.support.core.DynamicSessionFactoryImpl">

<propertyname="defaultTargetSessionFactory"ref="oracleSessionFactory"/>

<propertyname="targetSessionFactorys">

<map>

<entryvalue-ref="oracleSessionFactory"key="oracle"/>

<entryvalue-ref="mySQLSessionFactory"key="mysql"/>

</map>

</property>

</bean>


<!--自定义动态切换SessionFactory事务管理器,注入sessionFactory-->

<beanid="transactionManager"class="com.hoo.framework.spring.support.tx.DynamicTransactionManager">

<propertyname="sessionFactory"ref="sessionFactory"/>

</bean>


<!--配置事务的传播特性-->

<tx:adviceid="txAdvice"transaction-manager="transactionManager">

<tx:attributes>

<tx:methodname="add*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>

<tx:methodname="edit*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>

<tx:methodname="remove*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>

<tx:methodname="execute*"propagation="REQUIRED"rollback-for="java.lang.Exception"/>

<tx:methodname="*"read-only="true"/>

</tx:attributes>

</tx:advice>


<!--配置那些类、方法纳入到事务的管理-->

<aop:config>

<aop:pointcutexpression="execution(*com.hoo.**.service.impl.*.*(..))"id="transactionManagerMethod"/>

<aop:advisoradvice-ref="txAdvice"pointcut-ref="transactionManagerMethod"/>

</aop:config>

</beans>


配置也和我们之前的配置差不多,就是事务管理器部分注入的SessionFactory是我们自己定义的。

5、简单测试

@Test

publicvoidtestAdd(){

//主要就是这行代码它才是完成SessionFactory的切换

CustomerContextHolder.setCustomerType(CustomerContextHolder.SESSION_FACTORY_MYSQL);


DeviceInfoentity=newDeviceInfo();

entity.setSbbh(System.currentTimeMillis()+"");

entity.setIpdz("myipaddress2");

entity.setJd(1234);

try{

service.add(entity);

}catch(Exceptione){

e.printStackTrace();

}

}


经过测试发现可以查询数据,利用hibernate查询分页也可以生成对应数据库的分页语句。同样在服务层的方法中添加完操作后,特意抛出一个异常,数据也能正常回滚操作,所以DynamicTransactionManager也是起到了该有的作用!
这里我的测试用例是手动切换CustomerContextHolder.setCustomerType的,但实际开发中我们还是得用Spring的Aop中的Interceptor进行切面编程,完成动态切换SessionFactory。上一篇文章Spring3.3
整合Hibernate3、MyBatis3.2配置多数据源/动态切换数据源方法已经提到了(读者可以参考该篇博文中的第二节的3、7小节DataSourceMethodInterceptorMultipleDataSourceInterceptor),这里就不再赘述!

三、引发的问题

上述的实现如果在使用不当的情况下,在实际开发中很可能存在一些问题!

问题1是这样的,通常事务是在Service这层完成,这个应该是没有异议。倘若是这样的话,问题便出现了。而通常我们在MVC中的视图层中仅仅会调用Service中一个方法来完成所有当前业务的处理。如果这个Service中同时操作dbA、dbB两个数据库,事务提交的时候是用哪个数据库的事务呢?所以我们把不同数据库的操作放在一个方法,就会出现事务的问题的,除非两个数据库的事务能够同时回滚!
大致情景是:Service中的add4Oracle操作Oracle数据库,Service中的add4MySQL操作MySQL数据库,最后把Service中的add4Oracle、add4MySQL方法放到一个operation方法中,MVC视图层的业务控制调用Service中的operation。像这样的情况,如果add4Oracle或add4MySQL方法中的某一个方法出现异常,就需要把两个数据库事务都回滚。
解决办法就是在Service或Dao中的一个方法中同时操作两个数据库,手动完成事务控制。出现异常就全部回滚,没有异常就全部提交,只能这样牺牲下。

问题2是利用拦截器不能同时切换一个方法操作两个数据库的可能,例如一个service中的query方法即需要用queryMySQL,也需要queryOracle。那么解决办法就是在query方法中调用当前service的query4Oracle和query4Oracle,那样绕过去还是能利用拦截器进行动态切换的。

四、总结

要完成数据库方言的切换,我们就需要配置多个SessionFactory利用自己实现的DynamicSessionFactory和CustomerContextHolder完成SessionFactory的切换。利用DynamicTransactionManager完成当前Session的事务操作。通过对这些对象的操作和配置,最终可以完成SessionFactory的动态切换。实际使用中虽然有些问题出现,但是最终还是有解决方案,尽管有些不完美。所谓的完美的东西总有它不完美的地方,这样才算完美,比如断臂的维纳斯女神~!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: