Spring多数据源解决方案(研究,不确信是正确)开发目的:一个协同平台项目,多托管用户,单门户系统,每个托管用
Spring多数据源解决方案(研究,不确信是正确)
开发目的:一个协同平台项目,多托管用户,单门户系统,每个托管用户对应一个单一数据库,要求根据登陆用户的单位信息,自动选择操作数据库;同时,涉及跨库操作(比如跨库查询,跨库单据发送);同时事务处理必须支持这种多数据库模式,支持一些逻辑性不强的跨库事务,比如一些数据的发送和接收等
当然,如果说跨库操作只涉及到数据的发送和接受的话,也可以通过构建专门web service以及通信线程来处理,
开发环境: tomcat4.1,webwork2.2.4,spring2.0.4,hibernate3.1,osworkflow2.8,mysql5.0.19 由于正式发布的应用服务器是weblogic8.1,所以没有采用jdk5环境以及struts2
准备:
问题一由于有跨库操作,而且这种跨库操作无法预知,有的跨库操作完全是在逻辑运行中决定的,比如A托管用户或则C、D向B托管用户发了订单 ,B回复,这个回复是根据订单发送者来说的,具体到后台操作,是无法事先预知针对具体哪个后台物理数据库操作的.所以,也就是说,存在在业务执行过程中切换数据库的情况,传统的到注入设置dao类 sessionFactory、靠filter以及Interceptor设置线程安全的sessionFactory都无法完全达到设计目的
问题二事务,本来,我打算用JtaTransactionManager的,除了JtaTransactionManager,在开始时也实在没想到什么好的办法, 难道,JtaTransactionManager是唯一选择么?
步骤:
、因为问题一,所以系统在资源方面是多sessionFactory并存的方式,也就是多少个托管用户多少个sessionFactory,当然,事实上,这种应用型项目本身并发访问量不会太高(什么,很高么,总比不过广告联盟吧,哈哈).不用担心多了几个sessionFactory会对系统或则数据库造成多大影响.
然后复制,粘贴,修改jdbcUrl,象网站编辑一样..
假设,我配置了两个sessionFatory ,一个是mitSessionFactory,一个是testSessionFactory,如下:
这个我自己系统配置的一部分,系统会解析他,从而知晓究竟存在多少个sessionFactory,item's XmlNode中的id可以理解会托管客户的客户单位
编号,当然,这个配置完全可以忽略,直接从ApplicationContext中一样可以获取到这样的信息
在客户登陆的时候,系统要记录下该客户所属托管单位,然后通过上面的id找到bean's name ,最后获取这个sessionFactory,托管单位信息一般
都是个编号而已跟己方系统的托管用户管理相结合,一般是保存这个编号在session里面,也可以象asp.net一样,记录在安全凭证里,还不知道JAVA方面有没有类似实现,个人认为asp.net这个方法很值得采用,虽然MS号称安全系数+++++这个观点值得怀疑
首先建立一个类,HibernateSupport ,存放当前请求线程所需sessionFactory
java 代码?
public?class?HibernateSupport?{?? ????public?static?final?String?HIBERNATE_SESSIONIDKEY?=?"com.mit.hibernatesupport.factory.id";?? ????private?static?final?Logger?logger?=?Logger.getLogger(HibernateSupport.class);?? ????private?static?ApplicationContext?applicationContext?;?? ????private?static?boolean?singleSession=true;?? ????private?static?Map?factorybeanset;?? ????private?static?ThreadLocal?switchhistory;//在切换不同sessionFactory的时候用到?? ????private?static?ThreadLocal?idset;//记录当前默认的托管用户id,在实际使用中,这个是可以取消掉的?? ????private?static?ThreadLocal?curfactory;////当前正在使用的sessionFactory?? ????private?static?ThreadLocal?trace;//一个sessionFactory集合,用来记录这次线程调用了那些sessionFactory?? ????static?? ????{?? ????????idset?=?new?ThreadLocal();?? ????????curfactory?=?new?ThreadLocal();?? ????????trace?=?new?ThreadLocal();?? ????????switchhistory?=?new?ThreadLocal();?? ????}?? ?????? ????/**? ?????*?set?current?sessionfactory?for?the?Request? ?????*?@param??ServletContext? ?????*?@param??the?factory's?id?defined?in?courser.xml? ?????*/?? ????public?static?synchronized?void?setCurrent(ServletContext?context,Object?id)?? ????{?? ????????if?(idset.get()==null)?? ????????{?? ????????????idset.set(id);?? ????????????if?(factorybeanset.containsKey(id))?? ????????????{?? ????????????????if?(applicationContext==null)?? ????????????????{?? ?????????????????????applicationContext?=??? ????????????????????WebApplicationContextUtils?? ????????????????????????.getWebApplicationContext(context);?? ????????????????}?? ????????????????curfactory.set((SessionFactory)applicationContext?? ????????????????????????.getBean((String)factorybeanset.get(id)));?? ????????????????putTrace(idset.get(),(SessionFactory)curfactory.get());?? ????????????}?? ????????}?? ????}?? ?????? ????/**? ?????*?put?the?sessionfactory?to?tracemap? ?????*?@see?COPenSessionInViewFilter?release?sessionfactory?in?tracemap?? ?????*?@param??the?factory's?id?defined?in?courser.xml? ?????*?@param??hibernate's?sessionfactory? ?????*/?? ????private?static?void?putTrace(Object?id?,SessionFactory?factory)?? ????{?? ????????Map?tracemap?=?null;?? ????????if?(trace.get()==null)?? ????????{?? ????????????tracemap?=?new?HashMap();?? ????????????trace.set(tracemap);?? ????????}?? ????????else?? ????????{?? ????????????tracemap?=?(Map)trace.get();?? ????????}?? ????????if?(!tracemap.containsKey(id))?? ????????{?? ????????????tracemap.put(id,?factory);?? ????????}?? ????}?? ?????? ????/**? ?????*?switch?current?sessionfactory?? ?????*?@param??the?factory's?id?defined?in?courser.xml? ?????*/?? ????public?static?synchronized?void?swtichFactory(Object?id)?? ????{?? ????????if?(!idset.get().equals(id)??)?? ????????{?? ????????????if?(factorybeanset.containsKey(id))?? ????????????{?? ????????????????SessionFactory?oldfactory?=?(SessionFactory)curfactory.get();????????? ????????????????SessionFactory?newfactory?=?(SessionFactory)applicationContext?? ????????????????.getBean((String)factorybeanset.get(id));?? ????????????????curfactory.set(newfactory);?? ????????????????pushHistory(oldfactory);?? ????????????????putTrace(id,newfactory);?? ????????????????bindSessionFactory(newfactory);?? ????????????}?? ????????}?? ????}?? ?????? ????/**? ?????*?restore?sessionfactory?from?queue?of?switchhistory? ?????*/?? ????public?static?synchronized?void?restoreFactory()?? ????{?? ????????SessionFactory?factory?=?popHistory();?? ????????if?(factory!=null)?? ????????{?? ????????????curfactory.set(factory);?? ????????}?? ????}?? ????/**? ?????*?push?old?sessionfactory?to?swithhistory?after?swtichFactory? ?????*?@param?hibernate's?sessionfactory? ?????*/?? ????private?static?void?pushHistory(SessionFactory?sessionfactory)?? ????{?? ????????LinkedList?list?=?null;?? ????????if?(switchhistory.get()==null)?? ????????{?? ????????????list?=?new?LinkedList();?? ????????????switchhistory.set(list);?? ????????}?? ????????else?? ????????{?? ????????????list?=?(LinkedList)switchhistory.get();?? ????????}?? ????????list.add(0,sessionfactory);?? ?????????? ????}?? ????/**? ?????*?pop?sessionfactory?in?queue? ?????*/?? ????private?static?SessionFactory?popHistory()?? ????{?? ????????if?(switchhistory.get()!=null)?? ????????{?? ????????????LinkedList?list?=?(LinkedList)switchhistory.get();?? ????????????if?(list.size()>0)?? ????????????{?? ????????????????SessionFactory?factory?=?(SessionFactory)list.getFirst();?? ????????????????list.removeFirst();?? ????????????????return?factory;?? ????????????}?? ????????}?? ????????return?null;?? ????}?? ?????? ????public?static?Map?getTraceMap()?? ????{?? ????????if?(trace.get()!=null)?? ????????{?? ????????????return?(Map)trace.get();?? ????????}?? ????????return?null;?? ????}?? ?????? ????public?static?SessionFactory?getCurrentFactory()?? ????{?? ????????return?(SessionFactory)curfactory.get();?? ????}?? ?????? ????public?static?synchronized?void?release()?? ????{?? ????????idset.set(null);?? ????????curfactory.set(null);?? ????????switchhistory.set(null);?? ????????trace.set(null);?? ????}?? ?????? ????/**? ?????*?°ó?¨sessionFactoryμ?springμ?×ê?′1üàí? ?????*?@param?hibernate's?sessionfactory? ?????*/?? ????private?static?synchronized?boolean?bindSessionFactory(SessionFactory?sessionFactory)?? ????{?? ????????boolean?participate=false;;?? ????????if?(singleSession)?{?? ????????????//?single?session?mode?? ????????????if?(TransactionSynchronizationManager.hasResource(sessionFactory))?{?? ????????????????//?Do?not?modify?the?Session:?just?set?the?participate?flag.?? ????????????????participate?=?true;?? ????????????}?? ????????????else?{?? ????????????????logger.debug("Opening?single?Hibernate?Session?in?OpenSessionInViewFilter");?? ????????????????Session?session?=?getSession(sessionFactory);?? ????????????????if?(!TransactionSynchronizationManager.hasResource(sessionFactory))?? ????????????????{????? ????????????????????TransactionSynchronizationManager.bindResource(sessionFactory,?new?SessionHolder(session));?? ????????????????}?? ????????????}?? ????????}?? ????????else?{?? ????????????//?deferred?close?mode?? ????????????if?(SessionFactoryUtils.isDeferredCloseActive(sessionFactory))?{?? ????????????????//?Do?not?modify?deferred?close:?just?set?the?participate?flag.?? ????????????????participate?=?true;?? ????????????}?? ????????????else?{?? ????????????????SessionFactoryUtils.initDeferredClose(sessionFactory);?? ????????????}?? ????????}?? ????????return?participate;?? ????}?? ?????? ????//see?SessionFactoryUtils?? ????private?static??Session?getSession(SessionFactory?sessionFactory)?throws?DataAccessResourceFailureException?{?? ????????Session?session?=?SessionFactoryUtils.getSession(sessionFactory,?true);?? ????????FlushMode?flushMode?=?FlushMode.COMMIT;?? ????????if?(flushMode?!=?null)?{?? ????????????session.setFlushMode(flushMode);?? ????????}?? ????????return?session;?? ????}?? ?? ????public?static?synchronized?void?initSessionFactory(Map?res,Class?loadclass)?? ????{?? ????????factorybeanset?=res;?? ????}?? ?? ?????? }??
HibernateSupport这个类其他方法可以不管,暂时关注setCurrent这个方法
java 代码
if (idset.get()==null) { idset.set(id); if (factorybeanset.containsKey(id)) //factorybeanset包含的就是我自己系统配置中那一部分,key就是id,,value就是sessionFactory 在spring环境中的beanName { if (applicationContext==null) { applicationContext =WebApplicationContextUtils.getWebApplicationContext(context); } curfactory.set((SessionFactory)applicationContext.getBean((String)factorybeanset.get(id)));//设置当前的sessionFactory putTrace(idset.get(),(SessionFactory)curfactory.get());//put到当前线程的一个记录集 } }
然后,就要修改spring关于hibernate的一些支持类了,当然,也可以选择重新写一套dao支持类,呵呵,不过,显然,在spring基础上做一些小修改代价更小 HibernateAccessor(HibernateTemplate的基类)以及HibernateTransactionManager都是靠注入方式获取一个sessionFactory,显然,这套不适合了,修改之
多sessionFactory好做,配置在spring或则单独拿出来处理都可以,但是spring的HibernateDaoSupport 必须绑定一个sessionFactory,当然,我们完全可以写一个自己的HibernateDaoSupport ,但是既然用了spring的事务管理而又不想多花时间,还是将就改改用吧
java 代码
private SessionFactory sessionFactory; publicvoid setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; }
去掉HibernateAccessor和HibernateTransactionManager中的上述两段代码,当然,也别忘了毙掉两个类中的
afterPropertiesSet方法中那些检查代码
然后 ,ant打包就可以了,如果不想修改spring的代码,也可以单独把这几个类提出来另建jar包,我是单独提出来新建的,比如HibernateTransactionManager我改名成CHibernateTransactionManager,其他类似,但是包名必须是org.springframework.orm.hibernate3 ,谁也不想这么做,可是谁让sessionFactoryUtils中一个closexxxx方法没定义成public了??
如果想变更sessionFactoryUtils,奉劝算了吧..
然后可以做测试了,首先,部署测试的dao和service,先是事务部署
xml 代码
<beanid="transactionManager" alt="Spring多数据源解决方案(研究,不坚信是正确)"> <bean id="transactionManager"ref="transactionManager"/><property name="transactionAttributeSource"><ref local="transres"/></property></bean><bean ref="transactionInterceptor"/></bean>
我把事务配置在service层,事务管理采用的是我修改后的HibernateTransactionManager ,他不在要求sessionFactory的注入了
以下是测试的dao类和service类配置
<bean id="personDao" alt="Spring多数据源解决方案(研究,不坚信是正确)">public class PersonDaoImpl extends CHibernateDaoSupport implements PersonDao;public class PersonServiceImpl implements PersonService;public class DepartDaoImpl extends CHibernateDaoSupport implements DepartDao;public class DepartServiceImpl implements DepartService;
测试代码的目的是将两个实体类分别存放到mit和test库,假设默认用户是mit这个托管库的,这个方法就写在DepartServiceImpl类里面
public class DepartServiceImpl implements DepartService {private DepartDao dao;private PersonService service;..............................public void executeTest(Depart depart,Person person){dao.save(depart);HibernateSupport.switchFactory("test");service.save(person);HibernateSupport.restoreFactory();}..............................
上面代码中将depart对象存到mit库,而将person对象存到test库中.
事务 事务 ,没看见特别事务设置? 其实事务已经在运行了
如前面配置,事务我配置在了service层,那么当executeTest执行时,会启动针对mit库的事务(前面我们改了HibernateTransactionManager的sessionFactory获取方式),
dao.save(depart);//然后保存depart到mit库
HibernateSupport.switchFactory("test");//切换库到test
service.save(person);//调用personService的save方法,这也是个service,所以也会产生一个事务,而此时HibernateSupport.getCurrentFactory返回的sessionFactory已经是test库的了,所以,spring事务管理发现上下文中并没有针对test的事务,于是,会重新启动一个新的事务,这个service.save(person);方法执行完后,这个事务将会提交
HibernateSupport.restoreFactory();//切换回默认数据库
整个方法执行完后,针对mit的事务将会被提交
[color=green]如果service.save(person);发生异常,这两个事务就会被提交,一个简单跨库事务控制完成[color=green]
但是,问题也随之而来,如果在 HibernateSupport.restoreFactory();后,又进行了一些逻辑操作,那么发生异常时,而由于 service.save(person);这个事务已经被提交,也就是说,针对test的事务已经提交不会回滚了,这是个非常严重的问题。。
其实,也有解决方法,就是延迟提交,具体实现方式以后讲述
跨库事务之延迟提交
延迟事务,就是将事务延后提交,延迟的时间由事务管理器掌握。在我的系统中,只有跨库操作涉及到延迟提交,针对这种操作,我设计了一个基本的执行模型。就是如果一个逻辑中存在多个事务,将全部放到逻辑执行完以后提交,那么,既然如此,开始吧
PlatformTransactionManager是spring平台相关事务,比如HibernateTransactionManager都是继承于此类,为了达到延迟提交的目的,可以在AbstractPlatformTransactionManagershang上做修改达到目的
首先,说一下在spring中,通常的一个事务流程,
流程如下:初始化Transaction B,如果发现前面有其他Transaction ,比如 Transaction A,那么挂起TransactionA ,然后启动事务 ,当逻辑执行完后 ,commit,恢复挂起事务A,然后清除同步对象以及其他资源,如果执行发生异常,当然,异常发生后 rollback
延迟提交的设计思想是将事务都暂存在一个threadlocal的LIST里面,等逻辑执行完以后,再统一提交,那么首先在AbstractPlatformTransactionManager中设置一个threadlocal对象
private ThreadLocal lazytrace = new ThreadLocal();
LazyTransactionStatus用来管理需要延迟提交的事务
private static class LazyTransactionStatus{private java.util.List transliat;private int transnum;//代表未提交事务数量public LazyTransactionStatus(){transliat= new java.util.ArrayList();transnum=0;}public void newLazy(){transnum++;}public void push(TransactionStatus trasobj){objmap.add(trasobj);}public void removeLazy(TransactionStatus trasobj){transnum--;}public boolean canCommit(){if (transnum<1){return true;}elsereturn false;}public java.util.List getTransactionObjs(){return transliat;}}}
这就是local对象的存储内容.translist存放当前执行中的TransactionStatus实例
TransactionStatus顾名思义,事务状态,包含了事务、挂起资源等一系列信息
然后以事务提交为例
然后在AbstractTransactionManager增加如下两个方法
public final boolean isCommit(TransactionStatus status){if (lazytrace.get()!=null){LazyTransactionStatus lazystatus = (LazyTransactionStatus)lazytrace.get();lazystatus.removeLazy(status);return lazy.canCommit();}return true;}protected void begin(Object transaction, TransactionDefinition definition){doBegin(transaction,definition);LazyTransactionStatus lazystatus = null;if (lazytrace.get()==null){lazystatus = new LazyTransactionStatus();lazytrace.set(lazystatus);}else{lazystatus = (LazyTransactionStatus)lazytrace.get();}lazystatus.newLazy();}public final void registerTraceStatus(TransactionStatus status){LazyTransactionStatus lazystatus = null;if (lazytrace.get()==null){lazystatus = new LazyTransactionStatus();lazytrace.set(lazystatus);}else{lazystatus = (LazyTransactionStatus)lazytrace.get();}lazystatus.push(status);}
begin ,当一个事务开始时,将LazyTransactionStatus的transnum+1,表示又多了个刚开始,还未提交的事务
registerTraceStatus发生在commit的时候,注册这个事务到LazyTransactionStatus,同时,
注意 transnum表示的是未提交事务数量,所以当事务管理器执行commit表示要提交一个事务后,transnum将减一,如果减一后发现transnum<1,表示所有事务都提交了,那么,将所有事务提交。否则,不提交,继续等待...
关系如下:
begin->transnum+1 表示新事务诞生
registerTraceStatus(发生在commit的时候)->将准备提交的TransStatus放到LazyTransactionStatus,是的,这个事务要提交了,来吧,先注册一下
紧接着
isCommit()->将transnum-1,如果发现transnum小于1 ,表示闹够了,可以都提交滚蛋了
注意 ,transnum与LazyTransactionStatus的translist的链表长度在执行commit的时候是反方向发展的 一个增,一个减
好了,首先是注册事务数量,不用管了,在事务开始时begin方法它自己会调用了,
然后是修改AbstractPlatformTransactionManager的commit方法
public final void commit(TransactionStatus txstatus) throws TransactionException {this.registerTraceStatus(txstatus);if (this.isCommit(txstatus)){int error = 0;LazyTransactionStatus lazystatus = (LazyTransactionStatus)lazytrace.get();List statuslist = lazystatus.getTransactionObjs();for (int i=0;i<statuslist.size();i++){try{TransactionStatus status = (TransactionStatus)statuslist.get(i);if (status.isCompleted()) {error++;continue;//throw new IllegalTransactionStateException(//"Transaction is already completed - do not call commit or rollback more than once per transaction");}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;if (defStatus.isLocalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Transactional code has requested rollback");}processRollback(defStatus);error++;continue;}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {if (defStatus.isDebug()) {logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");}processRollback(defStatus);// Throw UnexpectedRollbackException only at outermost transaction boundary// or if explicitly asked to.if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {//throw new UnexpectedRollbackException(//"Transaction rolled back because it has been marked as rollback-only");error++;continue;}continue;}processCommit(defStatus);}catch (Exception ex){error++;ex.printStackTrace();continue;}}lazytrace.set(null);if (error>0)throw new IllegalTransactionStateException("Not commit all transaction");}}
this.registerTraceStatus(txstatus);//事务提交了,成了嫌疑犯,拖到threadlocal的LazyTransactionStatus监狱里面先关起来
if (isCommit()) //看看监狱的事务是不是满了,如果满了,就可以全放了
LazyTransactionStatus lazystatus = (LazyTransactionStatus)lazytrace.get();
List statuslist = lazystatus.getTransactionObjs();
for (int i=0;i<statuslist.size();i++)
{
????????????????????????? ........
???????????????????????? processCommit(defStatus);
//看来真的满了,都放了吧
回滚道理是一样的,不过不用判断了,直接全部放出来让他们滚吧
当然,目前这个实现只是个模型,真要实际应用,还需要做进一步封装,实际做,我用了OpenSessionInViewFilter,我已经做过测试,测试了了OpenSessionInViewFilter中singleSession为true和false两种情况,测试都通过了,呵呵
xml 代码<hibernatesupport> <itemid="mit"bean="mitSessionFactory"/> <itemid="test"bean="testSessionFactory"/> <hibernatesupport>
xml 代码?<bean?id="mitDataSource"?class="com.mchange.v2.c3p0.ComboPooledDataSource"?destroy-method="close">?? ??????<property?name="driverClass">?? ???????<value>com.mysql.jdbc.Driver</value>?? ??????</property>?? ??????<property?name="jdbcUrl">?? ???????<value>jdbc:mysql://127.0.0.1:3306/mitflow</value>?? ??????</property>?? ??????<property?name="user">?? ???????<value>root</value>?? ??????</property>?? ????...........................?? ?</bean>?? ?? <bean?id="mitSessionFactory"?? ??class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">?? ??<property?name="mappingResources">?? ????????..................................?? ????</props>?? ??</property>?? ???? ???<property?name="dataSource">?? ?????<ref?local="mitDataSource"?/>?? ???</property>?? ?</bean>??
?