springboot整合atomikos—单体项目多数据源整合
最近管理后台增加了其他数据库的一些操作,如果只是简单的切换数据源的话使用动态数据源就可以实现,但动态数据源切换容易,加上事务处理就非常麻烦,seata是分布式事务的解决方案,管理后台一个单体服务也用不了,手动写事务处理又非常麻烦,所以这里用了atomikos做多数据源的整合方案。
这里只做代码展示,原理这块无外乎XA协议,这块可以看mysql的知识梳理中对事务的讲解。
注意点:每个数据源要配置单独的dao package和xml。
一、引入atomikos jar包
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.0.6.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
二、配置文件中配置数据源连接信息
spring:
datasource:
db1:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
db2:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
db3:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db3?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false
username: root
password: root
三、准备数据源配置文件
要注意一定要配置MapperScan,扫描每个数据库对应的dao和xml。
同样的配置,每个数据源配置一份。
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.comm.dao.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class Db1DatabaseConfig {
@Value("${spring.datasource.db1.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.db1.url}")
private String url;
@Value("${spring.datasource.db1.username}")
private String username;
@Value("${spring.datasource.db1.password}")
private String password;
@Bean(name = "db1DataSource")
public DataSource dataSource() {
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(url);
mysqlXADataSource.setUser(username);
mysqlXADataSource.setPassword(password);
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXADataSource);
xaDataSource.setUniqueResourceName("db1DataSource");
xaDataSource.setMaxPoolSize(20);
xaDataSource.setMinPoolSize(3);
return xaDataSource;
}
//配置数据源
@Bean(name = "db1SqlSessionFactory")
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
//mapper.xml 的位置
Resource[] resources = pathMatchingResourcePatternResolver.getResources("classpath*:mybatis-mapper/db1/*.xml");
factoryBean.setMapperLocations(resources);
factoryBean.setDataSource(dataSource);
//防止pagehelper分页功能失效
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
factoryBean.setPlugins(new Interceptor[]{interceptor});
return factoryBean.getObject();
}
@Bean(name = "db1SqlSessionTemplate")
public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
四、准备transactionManager
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
@Configuration
public class TransactionManagerConfig {
/**
* 不管有多少个数据源只要配置一个 TransactionManager
*/
@Bean(name = "atomikosTransactionManager")
public TransactionManager atomikosTransactionManager(){
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
}
五、启动使用
和正常方法一样,在方法上加上@Transactional注解即可。不再演示了,atomikos的好处就是让你使用起来和单体服务中单个数据源一样,而且不用去额外搭建TM和注册中心等。
USING: com.atomikos.icatch.default_max_wait_time_on_shutdown = 922337203685477
USING: com.atomikos.icatch.allow_subtransactions = true
USING: com.atomikos.icatch.recovery_delay = 10000
USING: com.atomikos.icatch.automatic_resource_registration = true
USING: com.atomikos.icatch.oltp_max_retries = 5
USING: com.atomikos.icatch.client_demarcation = false
USING: com.atomikos.icatch.threaded_2pc = false
USING: com.atomikos.icatch.serial_jta_transactions = true
USING: com.atomikos.icatch.log_base_dir = ./
USING: com.atomikos.icatch.rmi_export_class = none
USING: com.atomikos.icatch.max_actives = 50
USING: com.atomikos.icatch.checkpoint_interval = 500
USING: com.atomikos.icatch.enable_logging = true
USING: com.atomikos.icatch.log_base_name = tmlog
USING: com.atomikos.icatch.max_timeout = 300000
USING: com.atomikos.icatch.trust_client_tm = false
USING: java.naming.factory.initial = com.sun.jndi.rmi.registry.RegistryContext
USING: com.atomikos.icatch.tm_unique_name = 172.17.170.113.tm
USING: com.atomikos.icatch.forget_orphaned_log_entries_delay = 86400000
USING: com.atomikos.icatch.oltp_retry_interval = 10000
USING: java.naming.provider.url = rmi://localhost:1099
USING: com.atomikos.icatch.force_shutdown_on_vm_exit = false
USING: com.atomikos.icatch.default_jta_timeout = 10000
Using default (local) logging and recovery...
db1DataSource: refreshed XAResource
db2DataSource: refreshed XAResource
db3DataSource: refreshed XAResource
022.06.17 16:00:43.388 INFO org.springframework.transaction.jta.JtaTransactionManager 501 checkUserTransactionAndTransactionManager - Using JTA UserTransaction: com.atomikos.icatch.jta.UserTransactionImp@668a32a4
2022.06.17 16:00:43.389 INFO org.springframework.transaction.jta.JtaTransactionManager 512 checkUserTransactionAndTransactionManager - Using JTA TransactionManager: com.atomikos.icatch.jta.UserTransactionManager@23b71d24
…………
Tomcat started on port(s): 9680 (http) with context path ''