spring下hibernate多数据库解决方案,以及跨库事务的尝试(已合并)

http://www.javaeye.com/topic/80697

开发目的:一个协同平台项目,多托管用户,单门户系统,每个托管用户对应一个单一数据库,要求根据登陆用户的单位信息,自动选择操作数据库;同时,涉及跨库操作(比如跨库查询,跨库单据发送);同时事务处理必须支持这种多数据库模式,支持一些逻辑性不强的跨库事务,比如一些数据的发送和接收等

当然,如果说跨库操作只涉及到数据的发送和接受的话,也可以通过构建专门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会对系统或则数据库造成多大影响.
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>  
然后复制,粘贴,修改jdbcUrl,象网站编辑一样..

假设,我配置了两个sessionFatory ,一个是mitSessionFactory,一个是testSessionFactory,如下:


xml 代码
<hibernatesupport> 
<item id="mit" bean="mitSessionFactory"/> 
<item id="test" bean="testSessionFactory"/> 
< hibernatesupport> 
这个我自己系统配置的一部分,系统会解析他,从而知晓究竟存在多少个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µÄ×ÊÔ´¹ÜÀí
   * 
   * @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这个方法
    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 代码
<bean id="transactionManager" 
class="com.mit.web.hibernate.CHibernateTransactionManager"/> 

<bean id="transres" class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource"> 
<property name="properties"> 
<props> 
<prop key="load*">PROPAGATION_REQUIRED,readOnlyprop> 
<prop key="save*">PROPAGATION_REQUIREDprop> 
<prop key="delete*">PROPAGATION_REQUIREDprop> 
<prop key="find*">PROPAGATION_REQUIRED,readOnlyprop> 
<prop key="query*">PROPAGATION_REQUIRED,readOnlyprop> 
<prop key="create*">PROPAGATION_REQUIREDprop> 
<prop key="set*">PROPAGATION_REQUIRED,readOnlyprop> 
<prop key="execute*">PROPAGATION_REQUIREDprop> 
props> 
property> 
bean> 

<bean id="transactionInterceptor" class=声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载
快乐渡过每一天,减肥坚持每一天