接上篇,为什么此种模式下,在spring托管CMT管理的JTA事务中,无法切换数据源,忙活了好久,对着日志流程和源代码,貌似问题出现在下面的代码中:
org.mybatis.spring .SqlSessionUtils
public static SqlSession getSqlSession方法:
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//7.当前在事务中,且session的holder存在,则取得当前事务的session
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (logger.isDebugEnabled()) {
logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
}
return holder.getSqlSession();
}
if (logger.isDebugEnabled()) {
logger.debug("Creating SqlSession with JDBC Connection [" + conn + "]");
}
//1.创建SqlSession
SqlSession session = sessionFactory.openSession(executorType, conn);
//2.判断当前有事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
if (logger.isDebugEnabled()) {
logger.debug("Registering transaction synchronization for SqlSession [" + session + "]");
}
//3.创建当前session的holder
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
//4.将session的holder注册到事务中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
//5.(8.)执行sql。。。。
public static void closeSqlSession方法:
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//6.(9.)释放掉当前事务的session
if ((holder != null) && (holder.getSqlSession() == session)) {
if (logger.isDebugEnabled()) {
logger.debug("Releasing transactional SqlSession [" + session + "]");
}
holder.released();
public void beforeCommit(boolean readOnly) 方法:
//10.session提交
if (TransactionSynchronizationManager.isActualTransactionActive()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]");
}
this.holder.getSqlSession().commit();
public void afterCompletion(int status) 方法:
//11.解除事务绑定的session并关闭
if (!this.holder.isOpen()) {
TransactionSynchronizationManager.unbindResource(this.sessionFactory);
try {
if (logger.isDebugEnabled()) {
logger.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
}
this.holder.getSqlSession().close();
在事务中,mybatis操作两个数据库的步骤流程:
1.创建SqlSession --第一个DAO,操作第一个DB
2.判断当前有事务
3.创建当前session的holder
4.将当前session的sessionFacotry的holder注册到事务中
5.执行sql。。。。
6.holder释放掉当前事务的session
7.当前在事务中,且sessionFactory的holder存在,则取得当前事务的session --第二个DAO,操作第二个DB
8.执行sql。。。。
9.释放掉当前事务的session
10.session提交
11.解除事务绑定的sessionFactory并关闭
可以知道在操作第二个DAO的时候取得的是,在事务中绑定的第一个SqlSession,整个Service用同一个SqlSession,所以无法切换数据源。
问题解决思路:通过上面的源代码
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//4.将session的holder注册到事务中
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
可以知道,事务绑定的是mybatis的当前SqlSessionFactory,如果SqlSessionFactory变了,则事务TransactionSynchronizationManager通过SqlSessionFactory(getResource(sessionFactory))获取
的SqlSessionHolder必定不是上一个事务中的,即holder.isSynchronizedWithTransaction()为false。
由此,可以找出一个方法解决,动态切换SqlSessionFactory
OK,代码如下:
/**
* 上下文Holder
*
*/
@SuppressWarnings("unchecked")
public class ContextHolder<T> {
private static final ThreadLocal contextHolder = new ThreadLocal();
public static <T> void setContext(T context)
{
contextHolder.set(context);
}
public static <T> T getContext()
{
return (T) contextHolder.get();
}
public static void clearContext()
{
contextHolder.remove();
}
}
/**
* 动态切换SqlSessionFactory的SqlSessionDaoSupport
*
* @see org.mybatis.spring.support.SqlSessionDaoSupport
*/
public class DynamicSqlSessionDaoSupport extends DaoSupport {
private Map<Object, SqlSessionFactory> targetSqlSessionFactorys;
private SqlSessionFactory defaultTargetSqlSessionFactory;
private SqlSession sqlSession;
private boolean externalSqlSession;
@Autowired(required = false)
public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
@Autowired(required = false)
public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
/**
* Users should use this method to get a SqlSession to call its statement
* methods This is SqlSession is managed by spring. Users should not
* commit/rollback/close it because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public final SqlSession getSqlSession() {
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder
.getContext());
if (targetSqlSessionFactory != null) {
setSqlSessionFactory(targetSqlSessionFactory);
} else if (defaultTargetSqlSessionFactory != null) {
setSqlSessionFactory(defaultTargetSqlSessionFactory);
}
return this.sqlSession;
}
/**
* {@inheritDoc}
*/
protected void checkDaoConfig() {
Assert.notNull(this.sqlSession,
"Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
public Map<Object, SqlSessionFactory> getTargetSqlSessionFactorys() {
return targetSqlSessionFactorys;
}
/**
* Specify the map of target SqlSessionFactory, with the lookup key as key.
* @param targetSqlSessionFactorys
*/
public void setTargetSqlSessionFactorys(Map<Object, SqlSessionFactory> targetSqlSessionFactorys) {
this.targetSqlSessionFactorys = targetSqlSessionFactorys;
}
public SqlSessionFactory getDefaultTargetSqlSessionFactory() {
return defaultTargetSqlSessionFactory;
}
/**
* Specify the default target SqlSessionFactory, if any.
* @param defaultTargetSqlSessionFactory
*/
public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
}
}
//每一个DAO由继承SqlSessionDaoSupport全部改为DynamicSqlSessionDaoSupport
public class xxxDaoImpl extends DynamicSqlSessionDaoSupport implements xxxDao {
public int insertUser(User user) {
return this.getSqlSession().insert("xxx.xxxDao.insertUser", user);
}
}
spring配置如下:
<!-- 创建数据源。 -->
<bean id="ds1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>testxa</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
</bean>
<bean id="ds2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>testxa1</value>
</property>
<property name="resourceRef">
<value>true</value>
</property>
</bean>
<!-- sqlSessionFactory工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds1" />
<property name="configLocation" value="classpath:config/mybatis-config.xml" />
</bean>
<bean id="sqlSessionFactory1" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="ds2" />
<property name="configLocation" value="classpath:config/mybatis-config.xml" />
</bean>
<!-- 动态切换SqlSessionFactory -->
<bean id="dynamicSqlSessionDaoSupport" class="com.suning.cmp.common.multidatasource.DynamicSqlSessionDaoSupport">
<property name="targetSqlSessionFactorys">
<map value-type="org.apache.ibatis.session.SqlSessionFactory">
<entry key="ds1" value-ref="sqlSessionFactory" />
<entry key="ds2" value-ref="sqlSessionFactory1" />
</map>
</property>
<property name="defaultTargetSqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="xxxDao" class="xxx.xxxDaoImpl" parent="dynamicSqlSessionDaoSupport"></bean>
Service测试代码如下:
@Transactional
public void testXA() {
ContextHolder.setContext("ds1");
xxxDao.insertUser(user);
ContextHolder.setContext("ds2");
xxxDao.insertUser(user);
}
通过Service代码,每个DAO访问都会调用getSqlSession()方法,此时就会调用DynamicSqlSessionDaoSupport的如下代码:
public final SqlSession getSqlSession() {
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(ContextHolder
.getContext());
if (targetSqlSessionFactory != null) {
setSqlSessionFactory(targetSqlSessionFactory);
} else if (defaultTargetSqlSessionFactory != null) {
setSqlSessionFactory(defaultTargetSqlSessionFactory);
}
return this.sqlSession;
}
起到动态切换SqlSessionFactory(每一个SqlSessionFactory对应一个DB)。OK,到此圆满解决,动态切换和事务这两个问题。
在此,我补充下为什么到用到动态切换,其实每一个SqlSessionFactory对应一个DB,而关于此DB操作的所有DAO对应此SqlSessionFactory,在Service中不去切换,直接用对应不同SqlSessionFactory
的DAO也可以,此种方式可以参考附件:《Spring下mybatis多数据源配置》
问题就在于,项目中不同DB存在相同的Table,动态可以做到只配置一个DAO,且操作哪个DB是通过路由Routing或者通过什么获取才能知道(延迟到Service时才知道对应哪个DB),此种情况用到动态切换,就显得方便很多。。。
分享到:
相关推荐
内容概要:本文详细介绍了基于MATLAB GUI界面和卷积神经网络(CNN)的模糊车牌识别系统。该系统旨在解决现实中车牌因模糊不清导致识别困难的问题。文中阐述了整个流程的关键步骤,包括图像的模糊还原、灰度化、阈值化、边缘检测、孔洞填充、形态学操作、滤波操作、车牌定位、字符分割以及最终的字符识别。通过使用维纳滤波或最小二乘法约束滤波进行模糊还原,再利用CNN的强大特征提取能力完成字符分类。此外,还特别强调了MATLAB GUI界面的设计,使得用户能直观便捷地操作整个系统。 适合人群:对图像处理和深度学习感兴趣的科研人员、高校学生及从事相关领域的工程师。 使用场景及目标:适用于交通管理、智能停车场等领域,用于提升车牌识别的准确性和效率,特别是在面对模糊车牌时的表现。 其他说明:文中提供了部分关键代码片段作为参考,并对实验结果进行了详细的分析,展示了系统在不同环境下的表现情况及其潜在的应用前景。
嵌入式八股文面试题库资料知识宝典-计算机专业试题.zip
嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_3.zip
内容概要:本文深入探讨了一款额定功率为4kW的开关磁阻电机,详细介绍了其性能参数如额定功率、转速、效率、输出转矩和脉动率等。同时,文章还展示了利用RMxprt、Maxwell 2D和3D模型对该电机进行仿真的方法和技术,通过外电路分析进一步研究其电气性能和动态响应特性。最后,文章提供了基于RMxprt模型的MATLAB仿真代码示例,帮助读者理解电机的工作原理及其性能特点。 适合人群:从事电机设计、工业自动化领域的工程师和技术人员,尤其是对开关磁阻电机感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解开关磁阻电机特性和建模技术的研究人员,在新产品开发或现有产品改进时作为参考资料。 其他说明:文中提供的代码示例仅用于演示目的,实际操作时需根据所用软件的具体情况进行适当修改。
少儿编程scratch项目源代码文件案例素材-剑客冲刺.zip
少儿编程scratch项目源代码文件案例素材-几何冲刺 转瞬即逝.zip
内容概要:本文详细介绍了基于PID控制器的四象限直流电机速度驱动控制系统仿真模型及其永磁直流电机(PMDC)转速控制模型。首先阐述了PID控制器的工作原理,即通过对系统误差的比例、积分和微分运算来调整电机的驱动信号,从而实现转速的精确控制。接着讨论了如何利用PID控制器使有刷PMDC电机在四个象限中精确跟踪参考速度,并展示了仿真模型在应对快速负载扰动时的有效性和稳定性。最后,提供了Simulink仿真模型和详细的Word模型说明文档,帮助读者理解和调整PID控制器参数,以达到最佳控制效果。 适合人群:从事电力电子与电机控制领域的研究人员和技术人员,尤其是对四象限直流电机速度驱动控制系统感兴趣的读者。 使用场景及目标:适用于需要深入了解和掌握四象限直流电机速度驱动控制系统设计与实现的研究人员和技术人员。目标是在实际项目中能够运用PID控制器实现电机转速的精确控制,并提高系统的稳定性和抗干扰能力。 其他说明:文中引用了多篇相关领域的权威文献,确保了理论依据的可靠性和实用性。此外,提供的Simulink模型和Word文档有助于读者更好地理解和实践所介绍的内容。
嵌入式八股文面试题库资料知识宝典-2013年海康威视校园招聘嵌入式开发笔试题.zip
少儿编程scratch项目源代码文件案例素材-驾驶通关.zip
小区开放对周边道路通行能力影响的研究.pdf
内容概要:本文探讨了冷链物流车辆路径优化问题,特别是如何通过NSGA-2遗传算法和软硬时间窗策略来实现高效、环保和高客户满意度的路径规划。文中介绍了冷链物流的特点及其重要性,提出了软时间窗概念,允许一定的配送时间弹性,同时考虑碳排放成本,以达到绿色物流的目的。此外,还讨论了如何将客户满意度作为路径优化的重要评价标准之一。最后,通过一段简化的Python代码展示了遗传算法的应用。 适合人群:从事物流管理、冷链物流运营的专业人士,以及对遗传算法和路径优化感兴趣的科研人员和技术开发者。 使用场景及目标:适用于冷链物流企业,旨在优化配送路线,降低运营成本,减少碳排放,提升客户满意度。目标是帮助企业实现绿色、高效的物流配送系统。 其他说明:文中提供的代码仅为示意,实际应用需根据具体情况调整参数设置和模型构建。
少儿编程scratch项目源代码文件案例素材-恐怖矿井.zip
内容概要:本文详细介绍了基于STM32F030的无刷电机控制方案,重点在于高压FOC(磁场定向控制)技术和滑膜无感FOC的应用。该方案实现了过载、过欠压、堵转等多种保护机制,并提供了完整的源码、原理图和PCB设计。文中展示了关键代码片段,如滑膜观测器和电流环处理,以及保护机制的具体实现方法。此外,还提到了方案的移植要点和实际测试效果,确保系统的稳定性和高效性。 适合人群:嵌入式系统开发者、电机控制系统工程师、硬件工程师。 使用场景及目标:适用于需要高性能无刷电机控制的应用场景,如工业自动化设备、无人机、电动工具等。目标是提供一种成熟的、经过验证的无刷电机控制方案,帮助开发者快速实现并优化电机控制性能。 其他说明:提供的资料包括详细的原理图、PCB设计文件、源码及测试视频,方便开发者进行学习和应用。
基于有限体积法Godunov格式的管道泄漏检测模型研究.pdf
嵌入式八股文面试题库资料知识宝典-CC++笔试题-深圳有为(2019.2.28)1.zip
少儿编程scratch项目源代码文件案例素材-几何冲刺 V1.5.zip
Android系统开发_Linux内核配置_USB-HID设备模拟_通过root权限将Android设备转换为全功能USB键盘的项目实现_该项目需要内核支持configFS文件系统
C# WPF - LiveCharts Project
少儿编程scratch项目源代码文件案例素材-恐怖叉子 动画.zip
嵌入式八股文面试题库资料知识宝典-嵌⼊式⼯程师⾯试⾼频问题.zip