之前一直自己写demo,然后用的是springboot整合jta,也没遇到啥问题,而且网上教程很多;这次在实际的一个项目中需要增加一个数据库连接,所以需要分布式事务了,结果一直报错-.-最终解决;
一.最开始没打算用到分布式事务的,就是动态的切换下数据源就行了:
1.准备配置类:
public enum MyDataSource {
DEFAULT, INDUSTRY
}
这里使用到了ThreadLocal,他的目的是在每次请求的线程中,做到独立线程中的数据共享;
package com.zc.www.config;
import com.zc.www.model.datasource.MyDataSource;
/**
* @Auther: gaoyang
* @Date: 2018/11/20 10:43
* @Description:数据源
*/
public class MyDataSourceHolder {
private final static ThreadLocal<MyDataSource> my = new ThreadLocal<>();
public static void set(MyDataSource myDataSource) {
my.set(myDataSource);
}
public static MyDataSource get() {
return my.get();
}
public static void clear() {
my.remove();
}
}
然后继承动态数据源,后面数据源就用这个:
package com.zc.www.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @Auther: gaoyang
* @Date: 2018/11/20 10:58
* @Description:动态数据源配置
*/
public class MyDynamic extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return MyDataSourceHolder.get();
}
}
2.配置数据源信息:
<bean id="dataSource"
class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close"
autowire="no">
<property name="fairQueue" value="false" />
<property name="minIdle" value="10" />
<property name="maxIdle" value="20" />
<property name="maxActive" value="100" />
<property name="initialSize" value="10" />
<property name="testOnBorrow" value="true" />
<property name="validationQuery" value="select version()" />
<property name="validationInterval" value="30000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="180" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="dataSource2"
class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close"
autowire="no">
<property name="fairQueue" value="false" />
<property name="minIdle" value="10" />
<property name="maxIdle" value="20" />
<property name="maxActive" value="100" />
<property name="initialSize" value="10" />
<property name="testOnBorrow" value="true" />
<property name="validationQuery" value="select 1" />
<property name="validationInterval" value="30000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="180" />
<property name="driverClassName" value="${jdbc.mysql.driver}" />
<property name="url" value="${jdbc.mysql.url}" />
<property name="username" value="${jdbc.mysql.username}" />
<property name="password" value="${jdbc.mysql.password}" />
</bean>
<bean class="com.zc.www.config.MyDynamic" id="MyDynamic">
<property name="defaultTargetDataSource" ref="dataSource"/>
<property name="targetDataSources">
<map key-type="com.zc.www.model.datasource.MyDataSource">
<entry key="DEFAULT" value-ref="dataSource"></entry>
<entry key="INDUSTRY" value-ref="dataSource2"></entry>
</map>
</property>
</bean>
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation"
value="classpath:mybatis-configuration.xml" />
<property name="dataSource" ref="MyDynamic" />
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor"
class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="postgresql" />
</bean>
</array>
</property>
<property name="globalConfig" ref="globalConfig"></property>
</bean>
<!-- 自动扫描注入类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper" />
<property name="sqlSessionFactoryBeanName"
value="sqlSessionFactory" />
</bean>
上面我用的是mybaitis-plus;
下面是切面的事务控制:
<!-- 事务相关控制 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="MyDynamic" />
</bean>
<tx:annotation-driven
transaction-manager="transactionManager" />
<tx:advice id="txAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<!-- 对业务层所有方法添加事务,除了以get、find、select开始的 -->
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 查询操作没有必要开启事务,给只读事务添加一个属性read-only -->
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
3.使用aop动态的切换数据源:
配置自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface DynamicSource {
MyDataSource value() default MyDataSource.INDUSTRY;
}
@Pointcut(value = "execution(* com.zc.www.mapper.*.*(..))")
public void datasource() {
}
@Before(value = "datasource()")
public void dynamicsource(JoinPoint j) {
Class declaringType = j.getSignature().getDeclaringType();
DynamicSource annotation = (DynamicSource) declaringType.getAnnotation(DynamicSource.class);
if (annotation != null) {
MyDataSourceHolder.set(annotation.value());
MyDataSource myDataSource = MyDataSourceHolder.get();
System.out.println(myDataSource);
} else {
MethodSignature signature = (MethodSignature) j.getSignature();
Method method = signature.getMethod();
if (method != null) {
DynamicSource annotation2 = method.getAnnotation(DynamicSource.class);
if (annotation2 != null) {
MyDataSourceHolder.set(annotation2.value());
}
}else{
MyDataSourceHolder.set(MyDataSource.DEFAULT);
}
}
}
@After(value = "datasource()")
public void dynamicAfter(){
MyDataSourceHolder.clear();
}
以上就实现了动态的切换数据源的功能;当然也可以使用多个sqlsession的方式,然后扫描不同的mapper包下接口的方式做多数据源;
以上方式的缺点就是如果你用的切面事务,或者使用注解开启事务的话,如果在一个方法中你操作了多个数据库的话,就会报找不到表的错误.因为事务是不可以在开启事务后进行切换数据源的;当然如果用不同sqlsession的方式也不可以,因为你没用到xa方式的提交事务方式,同样不支持;下面我们来看看怎么配置jta;
二.配置ssm整合jta分布式事务;
1.配置信息:
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>3.9.3</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
只需要以上两个包依赖;
<bean id="mysqlDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>mysql xa datasource</description>
<property name="uniqueResourceName">
<value>mysqlDataSource</value>
</property>
<property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">${jdbc.mysql.username}</prop>
<prop key="password">${jdbc.mysql.password}</prop>
<prop key="URL">${jdbc.mysql.url}</prop>
</props>
</property>
<property name="poolSize" value="20"/>
</bean>
<bean id="postDataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean"
init-method="init" destroy-method="close">
<description>post xa datasource</description>
<property name="uniqueResourceName">
<value>postDataSource</value>
</property>
<property name="xaDataSourceClassName" value="org.postgresql.xa.PGXADataSource" />
<property name="xaProperties">
<props>
<prop key="user">${jdbc.username}</prop>
<prop key="password">${jdbc.password}</prop>
<prop key="URL">${jdbc.url}</prop>
</props>
</property>
<property name="poolSize" value="20"/>
</bean>
<!-- atomikos事务管理器 -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
init-method="init" destroy-method="close">
<description>UserTransactionManager</description>
<property name="forceShutdown">
<value>true</value>
</property>
</bean>
<!-- atomikos用户事务实现 -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="300" />
</bean>
<!-- spring 事务管理器 -->
<bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<!--注入 atomikos事务管理器 -->
<property name="transactionManager">
<ref bean="atomikosTransactionManager" />
</property>
<!--注入 atomikos用户事务实现 -->
<property name="userTransaction">
<ref bean="atomikosUserTransaction" />
</property>
</bean>
<!-- spring事务模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref bean="springTransactionManager" />
</property>
</bean>
<bean id="mysql" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-configuration.xml"></property>
<property name="dataSource" ref="mysqlDataSource"></property>
</bean>
<bean id="post" class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-configuration.xml"></property>
<property name="dataSource" ref="postDataSource"></property>
</bean>
<!-- 自动扫描注入类 -->
<bean id="d1" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper2" />
<property name="sqlSessionFactoryBeanName"
value="mysql" />
</bean>
<bean id="d2" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zc.www.mapper" />
<property name="sqlSessionFactoryBeanName"
value="post" />
</bean>
这里我就没做动态切换了.将两个库不同的mapper接口放到了不同的包里;
<tx:annotation-driven
transaction-manager="springTransactionManager" />
<tx:advice id="txAdvice"
transaction-manager="springTransactionManager">
<tx:attributes>
<!-- 对业务层所有方法添加事务,除了以get、find、select开始的 -->
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" rollback-for="java.lang.Exception" />
<!-- 查询操作没有必要开启事务,给只读事务添加一个属性read-only -->
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="select*" read-only="true" />
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* com.zc.www.service.**.*.*(..))" />
<aop:advisor pointcut-ref="pointcut" advice-ref="txAdvice" />
</aop:config>
以上同样是切换控制事务;
注意:
我其实主要出问题的地方在这里:
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
org.postgresql.xa.PGXADataSource
这两个分别是mysql和postgresql的xa驱动,然后我直接就根据网上的粘贴过来了,殊不知驱动要跟数据库匹配;可能这里我犯傻的比较低级吧.
向之前我使用的mysql驱动,包下根本就没有该驱动,可能是新版本换了其他的全限制类名?
postgresql的驱动有是有,不过报数据源中url找不到的错了,然后试过才知道,也是驱动版本问题;我的数据库使用的是postgresql10版本,而我驱动使用的是9.x,不过之前是照常用的,这次使用xa驱动就报错了.然后换成了最新的4x.x的驱动,完美解决;
网上的教程很多.只有自己试过才知道~