一般的企业做一个项目,一般也就只会用到一个数据库,一个数据源就可以了。但是考虑到分库操作后,需要对多个数据库、数据表进行CRUD操作,此时则需要在一个服务层操作数据时,必须保证全局事务能够正常进行。
整体的项目布局:
主要的配置方式:
在一般的springboot+mybatis配置中,只需要额外添加pom依赖
org.springframework.boot
spring-boot-starter-jta-atomikos
server:
port: 80
mysql:
datasource:
test1:
url: jdbc:mysql://localhost:3306/banana1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
test-query: select 1
test2:
url: jdbc:mysql://localhost:3306/banana2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
test-query: select 1
主数据源配置,必须在配置的项中添加 @Primary 注解,其次无需配置事务,多个数据源的事务,交由 jta 统一处理。
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.alibaba.druid.pool.xa.DruidXADataSource;
import cn.linkpower.config.po.DBConfig1;
/**
* 第一个数据源配置 --- 不配置事务
* @author 76519
*
*/
@Configuration
@MapperScan(basePackages="cn.linkpower.dao.mapper1",sqlSessionFactoryRef="sqlSessionFactory1")
public class DataSourceConfig1 {
@Bean(name="dataSource1")
@Primary
public DataSource dataSource1(DBConfig1 dbConfig1) throws Exception {
DruidXADataSource druidXADataSource = new DruidXADataSource();
druidXADataSource.setUrl(dbConfig1.getUrl());
druidXADataSource.setUsername(dbConfig1.getUsername());
druidXADataSource.setPassword(dbConfig1.getPassword());
//使用AtomikosDataSourceBean封装com.alibaba.druid.pool.xa.DruidXADataSource
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(druidXADataSource);
//atomikos要求为每个AtomikosDataSourceBean编辑名称
atomikosDataSourceBean.setUniqueResourceName("dataSource1");
atomikosDataSourceBean.setPoolSize(5);
return atomikosDataSourceBean;
}
@Bean(name = "sqlSessionFactory1")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("dataSource1") DataSource masterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/bunana1/*Mapper.xml"));
return sessionFactory.getObject();
}
}
从数据源配置,和主数据源配置一样,不需要配置事务,事务交由 jta 同一处理。
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.alibaba.druid.pool.xa.DruidXADataSource;
import cn.linkpower.config.po.DBConfig2;
/**
* 第二个数据源配置 --- 不配置事务
* @author 76519
*
*/
@Configuration
@MapperScan(basePackages="cn.linkpower.dao.mapper2",sqlSessionFactoryRef="sqlSessionFactory2")
public class DataSourceConfig2 {
@Bean(name="dataSource2")
public DataSource dataSource1(DBConfig2 dbConfig2) throws Exception {
DruidXADataSource druidXADataSource = new DruidXADataSource();
druidXADataSource.setUrl(dbConfig2.getUrl());
druidXADataSource.setUsername(dbConfig2.getUsername());
druidXADataSource.setPassword(dbConfig2.getPassword());
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(druidXADataSource);
atomikosDataSourceBean.setUniqueResourceName("dataSource2");
atomikosDataSourceBean.setPoolSize(5);
return atomikosDataSourceBean;
}
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("dataSource2") DataSource masterDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/bunana1/*Mapper.xml"));
return sessionFactory.getObject();
}
}
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
@ConfigurationProperties(prefix="mysql.datasource.test1")
public class DBConfig1 {
private String driverClassName;
private String url;
private String username;
private String password;
}
此处只写明了 DBConfig1.java 类的属性,同时使用了 Lombok 插件简化代码。
注意:
配置类文件要想能够使用 application.yml 或者 application.properties 文件中的配置信息,必须使用 @Component 注解注释,否则会出现
Parameter 0 of method dataSource1 in cn.linkpower.config.DataSourceConfig1 required a bean of type 'cn.linkpower.config.po.DBConfig1' that could not be found
类似的报错信息。
import javax.transaction.UserTransaction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.jta.JtaTransactionManager;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
/**
* 整体事务配置
* @author 76519
*
*/
@Configuration
public class TXManagerConfig {
@Bean(name = "transactionManager")
@Primary
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
CREATE TABLE `users` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`descript` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`createtime` date DEFAULT NULL,
PRIMARY KEY (`userid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
注意:要想 java 事务能够回滚,必须保证数据库本身支持事务,即数据库表类型为 ENGINE = InnoDB
本身还有很多代码,像业务层和控制层等,此处的重点不在全部的代码编写上,所以未添加完整代码,需要完整代码的在下面的github中可以clone和下载。
自己完善了单数据源的CRUD操作,事务正常,操作两个数据源的事务,设定异常也都能进行回滚操作。
https://github.com/765199214/springboot-jta-mybatis
参考资料:《atomikos JTA/XA全局事务》、田守枝《4.0 atomikos JTA/XA全局事务》