org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE
org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java com.alibaba druid-spring-boot-starter 1.2.14 org.springframework.boot spring-boot-starter-test org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 com.github.pagehelper pagehelper-spring-boot-starter 1.3.0 org.springframework.boot spring-boot-starter-aop
# 数据源
spring:datasource:# master主数据源配置master:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8&serverTimezone=GMT%2B8username: rootpassword: root# cluster从数据源配置cluster:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/ds2?characterEncoding=UTF-8&serverTimezone=GMT%2B8username: rootpassword: root#pagehelper分页插件配置
pagehelper:master:helper-dialect: mysql #设置数据库类型reasonable: true #开启合理化:页码<=0 查询第一页,页码>=总页数查询最后一页support-methods-arguments: true #支持通过 Mapper 接口参数来传递分页参数params: count=countsql
Java代码方式创建主从数据源配置。 使用@Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入
/*** @ClassName: MasterDataSourceConfig* @Description: mysql主库配置类*/
@Configuration
@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {/*** 设置扫描包的路径*/public static final String PACKAGE = "cn.zysheep.dao.master";/*** Mapper配置文件路径*/public static final String MAPPER_LOCATION = "classpath*:mapper/master/*.xml";/*** 实体别名*/public static final String ENTITY_ALIASES = "cn.zysheep.entity";@Value("${spring.datasource.master.driverClassName}")private String driverClassName;@Value("${spring.datasource.master.url}")private String url;@Value("${spring.datasource.master.username}")private String user;@Value("${spring.datasource.master.password}")private String password;@Value("${pagehelper.master.helper-dialect}")private String helperDialect;@Value("${pagehelper.master.reasonable}")private String reasonable;/*** 创建数据源* @return DataSource* @Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入*/@Bean(name = "masterDataSource")@Primarypublic DataSource masterDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);return dataSource;}/*** 创建工厂*@param dataSource*@throws Exception*@return SqlSessionFactory*/@Bean(name = "masterSqlSessionFactory")@Primarypublic SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES);org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();// 字段值为空也可以插入configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setCallSettersOnNulls(true);// mybatis logconfiguration.setLogImpl(StdOutImpl.class);sessionFactory.setConfiguration(configuration);PageInterceptor pageInterceptor = new PageInterceptor();// 分页配置Properties p = new Properties();p.setProperty("helperDialect", helperDialect);p.setProperty("reasonable", reasonable);pageInterceptor.setProperties(p);sessionFactory.setPlugins(pageInterceptor);return sessionFactory.getObject();}/*** 创建事务*@param dataSource*@return DataSourceTransactionManager*/@Bean(name = "masterTransactionManager")@Primarypublic DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}/*** 创建模板*@param sqlSessionFactory*@return SqlSessionTemplate*/@Bean(name = "masterSqlSessionTemplate")@Primarypublic SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
/*** @ClassName: DataSourceConfig* @Description: mysql从库配置类*/
@Configuration
@MapperScan(basePackages = ClusterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "clusterSqlSessionTemplate")
public class ClusterDataSourceConfig {/*** 设置扫描包的路径*/public static final String PACKAGE = "cn.zysheep.dao.cluster";/*** Mapper配置文件路径*/public static final String MAPPER_LOCATION = "classpath*:mapper/cluster/*.xml";/*** 实体别名*/public static final String ENTITY_ALIASES = "cn.zysheep.entity";@Value("${spring.datasource.cluster.driverClassName}")private String driverClassName;@Value("${spring.datasource.cluster.url}")private String url;@Value("${spring.datasource.cluster.username}")private String user;@Value("${spring.datasource.cluster.password}")private String password;@Value("${pagehelper.master.helper-dialect}")private String helperDialect;@Value("${pagehelper.master.reasonable}")private String reasonable;/*** 创建数据源*@return DataSource*/@Bean(name = "clusterDataSource")public DataSource masterDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driverClassName);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);return dataSource;}/*** 创建工厂*@param dataSource*@throws Exception*@return SqlSessionFactory*/@Bean(name = "clusterSqlSessionFactory")public SqlSessionFactory masterSqlSessionFactory(@Qualifier("clusterDataSource") DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES);org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();// 字段值为空也可以插入configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setCallSettersOnNulls(true);// mybatis logconfiguration.setLogImpl(StdOutImpl.class);sessionFactory.setConfiguration(configuration);PageInterceptor pageInterceptor = new PageInterceptor();// 分页配置Properties p = new Properties();p.setProperty("helperDialect", helperDialect);p.setProperty("reasonable", reasonable);pageInterceptor.setProperties(p);sessionFactory.setPlugins(pageInterceptor);return sessionFactory.getObject();}/*** 创建事务管理器*@param dataSource*@return DataSourceTransactionManager*/@Bean(name = "clusterTransactionManager")public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("clusterDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}/*** 创建模板*@param sqlSessionFactory*@return SqlSessionTemplate*/@Bean(name = "clusterSqlSessionTemplate")public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("clusterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
启动类添加@EnableTransactionManagement注解,开启注解事务
@EnableTransactionManagement
@SpringBootApplication(exclude = PageHelperAutoConfiguration.class)
public class MultiDataSourceApplication {public static void main(String[] args) {SpringApplication.run(MultiDataSourceApplication.class, args);}
}
在service层类或者方法上添加@Transactional注解。并指定属性事务管理器transactionManager的值,根据使用不同的数据源,选择不同的数据事务管理器
@Transactional(transactionManager = "masterTransactionManager")
public void insertUser() {// TODO 添加用户业务操作
}@Transactional(transactionManager = "clusterTransactionManager")
public void listUser() {// TODO 查询用户业务操作
}
思考: @Transactional只能指定一个事务管理器,并且注解不允许重复,所以就只能使用一个数据源的事务管理器了。那么对于一个方法涉及到多个数据源操作需要保证事务一致性的怎么办呢?
基于注解+AOP使用编程式事务的方式实现跨数据源事务
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiDataSourceTransactional {/*** 事务管理器数组*/String[] transactionManagers();
}
首先多个数据源的事务分别都开起来,然后各事务分别去执行对应的sql(此所谓第一阶段提交),最后如果都成功就把事务全部提交,只要有一个失败就把事务都回滚——此所谓第二阶段提交。
/*** 需要注意的点:* 1)声明事务和提交事务或者回滚事务的顺序应该相反的,就是先进后出,所以采用了栈来存储。* 2)线程执行结束后记得清空本地变量。* 3)可以参照@Transactional的那些属性升级功能,比如隔离级别回滚异常等。*/
@Component
@Aspect
public class MultiTransactionAop {/*** 线程本地变量:为什么使用栈?※为了达到后进先出的效果※*/private static final ThreadLocal>> THREAD_LOCAL = new ThreadLocal<>();private static Logger logger = LoggerFactory.getLogger(MultiTransactionAop.class);/*** 用于获取事务管理器*/@Autowiredprivate ApplicationContext applicationContext;/*** 事务声明*/private DefaultTransactionDefinition def = new DefaultTransactionDefinition();{// 非只读模式def.setReadOnly(false);// 事务隔离级别:采用数据库的def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);// 事务传播行为def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);}/*** 切点*/@Pointcut("@annotation(cn.zysheep.datasource.anntation.MultiDataSourceTransactional)")public void pointcut() {}/*** 声明事务** @param transactional 注解*/@Before("pointcut() && @annotation(transactional)")public void before(MultiDataSourceTransactional transactional) {// 根据设置的事务名称按顺序声明,并放到ThreadLocal里String[] transactionManagerNames = transactional.transactionManagers();Stack
在事务的方法上添加前面我们的多数据源事务注解
/*** 测试多数据源事务*/@MultiDataSourceTransactional(transactionManagers = {"masterTransactionManager", "clusterTransactionManager"})@Overridepublic void saveUser() {String rid = UUID.randomUUID().toString();User user = new User();user.setId(rid);userMapper.saveUser(user);Log log = new Log();log.setId(rid);logMapper.saveLog(log);throw new RuntimeException();}
上一篇:新月3-1逆转绝杀十人塞帕罕,米特罗维奇、哈姆丹补时破门 新月第二集 新月第3期视频
下一篇:欧联淘汰附加赛首回合:费耶诺德1-1罗马,派尚、卢卡库破门 欧联杯费耶诺德2-2中日德兰 欧联杯半决赛罗马淘汰了谁