做网站只买一个程序,免费的短视频素材库,制作微网站公司,淘宝推广运营引言
在互联网开发公司中#xff0c;往往伴随着业务的快速迭代#xff0c;程序员可能没有过多的时间去思考技术扩展的相关问题#xff0c;长久下来导致技术过于单一。为此最近在学习互联网思维#xff0c;从相对简单的功能开始做总结#xff0c;比如非常常见的基础数据的…引言
在互联网开发公司中往往伴随着业务的快速迭代程序员可能没有过多的时间去思考技术扩展的相关问题长久下来导致技术过于单一。为此最近在学习互联网思维从相对简单的功能开始做总结比如非常常见的基础数据的后台管理那么涉及到多数据源的情况又会有哪些问题呢
思考1在业务中如何更加灵活方便的切换数据源呢
思考2多数据源之间的事务如何保证呢
思考3这种多数据源的分布式事务实现思路有哪些
本篇文章的重点也就是多数据源问题总结为以下三种方式
使用Spring提供的AbstractRoutingDataSource
准备工作
首先AbstractRoutingDataSource是jdbc包提供的需要引入依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId
/dependency
自定义一个类继承AbstractRoutingDataSource重写关键方法
Component
Primary
public class DynamicDatasource extends AbstractRoutingDataSource
{public static ThreadLocalString dataSourceName new ThreadLocal();AutowiredDataSource dataSource1;AutowiredDataSource dataSource2;Overrideprotected Object determineCurrentLookupKey(){return dataSourceName.get();}Overridepublic void afterPropertiesSet(){MapObject, Object targetDataSources new HashMap();targetDataSources.put(W, dataSource1); //写数据库主库targetDataSources.put(R, dataSource2); //读数据库从库super.setTargetDataSources(targetDataSources);super.setDefaultTargetDataSource(dataSource1);super.afterPropertiesSet();}
}
配置多个数据源
Configuration
public class DataSourceConfig
{BeanConfigurationProperties(prefix spring.datasource.datasource1)public DataSource dataSource1(){return DruidDataSourceBuilder.create().build();}BeanConfigurationProperties(prefix spring.datasource.datasource2)public DataSource dataSource2(){return DruidDataSourceBuilder.create().build();}Beanpublic DataSourceTransactionManager transactionManager1(DynamicDatasource dataSource){DataSourceTransactionManager dataSourceTransactionManager new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}Beanpublic DataSourceTransactionManager transactionManager2(DynamicDatasource dataSource){DataSourceTransactionManager dataSourceTransactionManager new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}
}
application.yml参考配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedatasource1:url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezoneUTCuseUnicodetruecharacterEncodingUTF8useSSLfalseusername: rootpassword: 123666initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverdatasource2:url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezoneUTCuseUnicodetruecharacterEncodingUTF8useSSLfalseusername: rootpassword: 123666initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver min-idle最小空闲连接不会被销毁的数据库连接。注意如果设置的太小当客户端连接多的情况下就需要新创建数据库连接是一个比较耗时的操作可能导致客户端连接超时。设置太大会占用系统资源 max-active最大活跃数官网建议配置正在使用的数据库连接数 / 配置的这个值 85% test-on-borrow每次连接时都进行检查生产上配置为true会影响性能建议false默认也是false test-on-return每次归还连接时进行检查同样影响性能同上 生产上建议上面两个参数设置falsetestWhileIdle设置为true间隔一段时间检查连接是否可用 空闲时间大于timeBetweenEvictionRunsMillis默认1分钟检查一次检查发现连接失效也不会马上删除而是空闲时间超过minEvictableIdleTimeMillis最小空闲时间默认30分钟自动删除 maxEvictableIdleTimeMillis最大空闲时间默认7小时。空闲连接时间过长数据库就会自动把连接关闭Druid为了防止从连接池中拿到被数据库关闭的连接设置了这个参数超过时间强行关闭连接 使用测试
方案一直接在方法中设置数据源标识简单实现功能缺点也很明显
Service
public class FriendServiceImpl implements FriendService
{AutowiredFriendMapper friendMapper;Overridepublic ListFriend list(){DynamicDatasource.dataSourceName.set(R);return friendMapper.list();}Overridepublic void save(Friend friend){DynamicDatasource.dataSourceName.set(W);friendMapper.save(friend);}
}
方案二使用自定义注解AOP实现适合不同业务的多数据源场景
// 1、自定义注解
Target({ElementType.METHOD, ElementType.TYPE})
Retention(RetentionPolicy.RUNTIME)
public interface WR
{String value() default W;
}// 2、切面配置类
Component
Aspect
public class DynamicDataSourceAspect
{Before(within(com.example.dynamicdatasource.service.impl.*) annotation(wr))public void before(JoinPoint joinPoint, WR wr){String value wr.value();DynamicDatasource.dataSourceName.set(value);System.out.println(value);}
}// 3、使用注解测试
Service
public class FriendServiceImpl implements FriendService
{AutowiredFriendMapper friendMapper;WR(R)Overridepublic ListFriend list(){
// DynamicDatasource.dataSourceName.set(R);return friendMapper.list();}WR(W)Overridepublic void save(Friend friend){
// DynamicDatasource.dataSourceName.set(W);friendMapper.save(friend);}
}
方案三使用MyBatis插件适合相同业务读写分离的业务场景
import com.example.dynamicdatasource.DynamicDatasource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;import java.util.Properties;Component
Intercepts({Signature(type Executor.class, method update, args {MappedStatement.class, Object.class}),Signature(type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class})})
public class DynamicDataSourcePlugin implements Interceptor
{Overridepublic Object intercept(Invocation invocation)throws Throwable{Object[] objects invocation.getArgs();MappedStatement ms (MappedStatement)objects[0];if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)){DynamicDatasource.dataSourceName.set(R);}else{DynamicDatasource.dataSourceName.set(W);}return invocation.proceed();}Overridepublic Object plugin(Object target){if (target instanceof Executor){return Plugin.wrap(target, this);}else{return target;}}Overridepublic void setProperties(Properties properties){}
} 使用MyBatis注册多个sqlSessionFactory
实现思路Spring集成多个MyBatis框架指定不同的扫描包、不同的数据源
准备工作
读库和写库分别添加配置类扫描不同的包路径
Configuration
MapperScan(basePackages com.example.dynamicmybatis.mapper.r, sqlSessionFactoryRef rSqlSessionFactory)
public class RMyBatisConfig
{BeanConfigurationProperties(prefix spring.datasource.datasource2)public DataSource dataSource2(){return DruidDataSourceBuilder.create().build();}Beanpublic DataSourceTransactionManager rTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource2());return dataSourceTransactionManager;}Beanpublic SqlSessionFactory rSqlSessionFactory()throws Exception{final SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource2());return sqlSessionFactoryBean.getObject();}Beanpublic TransactionTemplate rTransactionTemplate(){return new TransactionTemplate(rTransactionManager());}
}Configuration
MapperScan(basePackages com.example.dynamicmybatis.mapper.w, sqlSessionFactoryRef wSqlSessionFactory)
public class WMyBatisConfig
{BeanConfigurationProperties(prefix spring.datasource.datasource1)public DataSource dataSource1(){return DruidDataSourceBuilder.create().build();}Beanpublic DataSourceTransactionManager wTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource1());return dataSourceTransactionManager;}Beanpublic SqlSessionFactory wSqlSessionFactory()throws Exception{final SqlSessionFactoryBean sqlSessionFactoryBean new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource1());return sqlSessionFactoryBean.getObject();}Beanpublic TransactionTemplate wTransactionTemplate(){return new TransactionTemplate(wTransactionManager());}
}
写库和读库使用两个Mapper类在不同的包下
public interface RFriendMapper
{Select(select * from friend)ListFriend list();Insert(insert into friend(name) values(#{name}))void save(Friend friend);
}public interface WFriendMapper
{Select(select * from friend)ListFriend list();Insert(insert into friend(name) values(#{name}))void save(Friend friend);
}
使用测试
Service
public class FriendServiceImpl implements FriendService
{AutowiredRFriendMapper rFriendMapper;AutowiredWFriendMapper wFriendMapper;Overridepublic ListFriend list(){return rFriendMapper.list();}Overridepublic void save(Friend friend){wFriendMapper.save(friend);}
}
思考多数据源的事务问题
public void saveW(Friend friend)
{friend.setName(gaoW);wFriendMapper.save(friend);
}public void saveR(Friend friend)
{friend.setName(gaoR);rFriendMapper.save(friend);
}Transactional
// Transactional(transactionManager wTransactionManager)
Override
public void saveAll(Friend friend)
{saveW(friend);saveR(friend);int a 1 / 0;
}
存在多个事务管理器的情况直接使用Transactional注解是不行的Spring不知道使用哪个事务管理器会报错。但是指定了事务管理器后仅当前事务管理器负责的部分支持回滚还是存在问题。
在特定场景下直接指定事务管理器名称的方式可以生效保证数据一致的意思
Transactional(transactionManager wTransactionManager)
Override
public void saveAll(Friend friend)
{saveW(friend);saveR(friend);int a 1 / 0;
}
1、saveW方法内部异常saveW发生异常事务不提交数据一致
2、saveR方法内部异常事务管理器回滚saveW的更新saveR异常未提交数据一致
3、saveW和saveR方法中间的业务发生异常事务管理器回滚saveW的更新saveR未提交数据一致
4、saveW和saveR方法后面的业务发生异常事务管理器回滚saveW的更新saveR已提交数据不一致
Spring提供的编程式事务解决方案
Service
public class FriendServiceImpl implements FriendService
{AutowiredRFriendMapper rFriendMapper;AutowiredWFriendMapper wFriendMapper;AutowiredTransactionTemplate rTransactionTemplate;AutowiredTransactionTemplate wTransactionTemplate;public void saveW(Friend friend){friend.setName(gaoW);wFriendMapper.save(friend);}public void saveR(Friend friend){friend.setName(gaoR);rFriendMapper.save(friend);}Overridepublic void saveAll2(Friend friend){wTransactionTemplate.execute(wstatus - {rTransactionTemplate.execute(rstatus - {try{saveW(friend);saveR(friend);
// int a 1 / 0;}catch (Exception e){e.printStackTrace();wstatus.setRollbackOnly();rstatus.setRollbackOnly();return false;}return true;});return true;});}
}
Spring支持的声明式事务解决方案分布式事务变种实现
Service
public class FriendServiceImpl implements FriendService
{AutowiredRFriendMapper rFriendMapper;AutowiredWFriendMapper wFriendMapper;AutowiredTransactionTemplate rTransactionTemplate;AutowiredTransactionTemplate wTransactionTemplate;Overridepublic ListFriend list(){return rFriendMapper.list();}Overridepublic void save(Friend friend){wFriendMapper.save(friend);}public void saveW(Friend friend){friend.setName(gaoW);wFriendMapper.save(friend);}public void saveR(Friend friend){friend.setName(gaoR);rFriendMapper.save(friend);}Transactional(transactionManager wTransactionManager)Overridepublic void saveAll1(Friend friend){FriendService friendService (FriendService)AopContext.currentProxy();friendService.saveAllR(friend);}Transactional(transactionManager rTransactionManager)Overridepublic void saveAllR(Friend friend){saveW(friend);saveR(friend);
// int a 1 / 0;}
}EnableAspectJAutoProxy(exposeProxy true) //暴露代理对象
public class DynamicMybatisApplication { 注意调用saveAllR方式时需要使用代理对象直接调用本类的其他方法事务不会生效 Autowired自动注入自己获取代理对象这种方式在springboot2.6以后有循环依赖报错需要改配置按照错误提示添加配置设置参数为true即可 上面这两种事务的解决方式适用场景
只涉及到两三个数据源并且多数据源事务的场景不多同时公司又不希望引入其他组件安全性问题考虑那么就可以使用这种方式实现分布式事务。当然分布式事务最好的解决方案肯定是通过第三方组件比如Seata 使用dynamic-datasource框架
dynamic-datasource是属于苞米豆生态圈的
基于Springboot的多数据源组件功能强悍支持Seata事务 支持数据源分组适用多库、读写分离、一主多从实现了负载均衡轮询/随机等场景 提供自定义数据源方案比如从数据库加载 提供项目启动后动态增加和删除数据源方案可以添加管理后台页面灵活调整 提供MyBatis环境下的纯读写分离方案 提供本地多数据源事务方案 提供基于Seata的分布式事务方案注意不能和原生spring事务混用 等等 多数据源实现方式就是通过继承AbstractRoutingDataSource的这种方式 数据源切换通过AOP自定义注解实现的 使用示例
Service
public class FriendServiceImpl implements FriendService
{AutowiredFriendMapper friendMapper;OverrideDS(slave)public ListFriend list(){return friendMapper.list();}OverrideDS(master)DSTransactionalpublic void save(Friend friend){friendMapper.save(friend);}DS(master)DSTransactionalpublic void saveAll(){// 执行多数据源的操作}
}
application.yml参考配置
spring:datasource:dynamic:#设置默认的数据源或者数据源组,默认值即为masterprimary: master#严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源strict: falsedatasource:master:url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezoneUTCuseUnicodetruecharacterEncodingUTF8useSSLfalseusername: rootpassword: 123666initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driverslave_1:url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezoneUTCuseUnicodetruecharacterEncodingUTF8useSSLfalseusername: rootpassword: 123666initial-size: 1min-idle: 1max-active: 20test-on-borrow: truedriver-class-name: com.mysql.cj.jdbc.Driver
文章转载自: http://www.morning.tbbxn.cn.gov.cn.tbbxn.cn http://www.morning.sgnjg.cn.gov.cn.sgnjg.cn http://www.morning.wmmqf.cn.gov.cn.wmmqf.cn http://www.morning.fhntj.cn.gov.cn.fhntj.cn http://www.morning.xqltq.cn.gov.cn.xqltq.cn http://www.morning.ckzjl.cn.gov.cn.ckzjl.cn http://www.morning.wjlkz.cn.gov.cn.wjlkz.cn http://www.morning.glkhx.cn.gov.cn.glkhx.cn http://www.morning.tmtrl.cn.gov.cn.tmtrl.cn http://www.morning.c7496.cn.gov.cn.c7496.cn http://www.morning.crdtx.cn.gov.cn.crdtx.cn http://www.morning.gnkdp.cn.gov.cn.gnkdp.cn http://www.morning.dpflt.cn.gov.cn.dpflt.cn http://www.morning.xzgbj.cn.gov.cn.xzgbj.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.spftz.cn.gov.cn.spftz.cn http://www.morning.lkbyq.cn.gov.cn.lkbyq.cn http://www.morning.nysjb.cn.gov.cn.nysjb.cn http://www.morning.wspyb.cn.gov.cn.wspyb.cn http://www.morning.jcfg.cn.gov.cn.jcfg.cn http://www.morning.rzrbw.cn.gov.cn.rzrbw.cn http://www.morning.bpmdz.cn.gov.cn.bpmdz.cn http://www.morning.c7507.cn.gov.cn.c7507.cn http://www.morning.ktrzt.cn.gov.cn.ktrzt.cn http://www.morning.ggnfy.cn.gov.cn.ggnfy.cn http://www.morning.nqyfm.cn.gov.cn.nqyfm.cn http://www.morning.hdwjb.cn.gov.cn.hdwjb.cn http://www.morning.ctrkh.cn.gov.cn.ctrkh.cn http://www.morning.ckxd.cn.gov.cn.ckxd.cn http://www.morning.wjhnx.cn.gov.cn.wjhnx.cn http://www.morning.hmpxn.cn.gov.cn.hmpxn.cn http://www.morning.tmsxn.cn.gov.cn.tmsxn.cn http://www.morning.dspqc.cn.gov.cn.dspqc.cn http://www.morning.nnwnl.cn.gov.cn.nnwnl.cn http://www.morning.xgbq.cn.gov.cn.xgbq.cn http://www.morning.spqtq.cn.gov.cn.spqtq.cn http://www.morning.kcrw.cn.gov.cn.kcrw.cn http://www.morning.lzdbb.cn.gov.cn.lzdbb.cn http://www.morning.c7500.cn.gov.cn.c7500.cn http://www.morning.qwgct.cn.gov.cn.qwgct.cn http://www.morning.xysdy.cn.gov.cn.xysdy.cn http://www.morning.mbrbg.cn.gov.cn.mbrbg.cn http://www.morning.lwtfx.cn.gov.cn.lwtfx.cn http://www.morning.jcfqg.cn.gov.cn.jcfqg.cn http://www.morning.sfhjx.cn.gov.cn.sfhjx.cn http://www.morning.zzfqn.cn.gov.cn.zzfqn.cn http://www.morning.pzpj.cn.gov.cn.pzpj.cn http://www.morning.yfstt.cn.gov.cn.yfstt.cn http://www.morning.ycpnm.cn.gov.cn.ycpnm.cn http://www.morning.bdgb.cn.gov.cn.bdgb.cn http://www.morning.nzkkh.cn.gov.cn.nzkkh.cn http://www.morning.hptbp.cn.gov.cn.hptbp.cn http://www.morning.cljpz.cn.gov.cn.cljpz.cn http://www.morning.twpq.cn.gov.cn.twpq.cn http://www.morning.dhxnr.cn.gov.cn.dhxnr.cn http://www.morning.zqfjn.cn.gov.cn.zqfjn.cn http://www.morning.rjjys.cn.gov.cn.rjjys.cn http://www.morning.lveyue.com.gov.cn.lveyue.com http://www.morning.fldrg.cn.gov.cn.fldrg.cn http://www.morning.qczpf.cn.gov.cn.qczpf.cn http://www.morning.lekbiao.com.gov.cn.lekbiao.com http://www.morning.rnpnn.cn.gov.cn.rnpnn.cn http://www.morning.qgjp.cn.gov.cn.qgjp.cn http://www.morning.mdpkf.cn.gov.cn.mdpkf.cn http://www.morning.rzrbw.cn.gov.cn.rzrbw.cn http://www.morning.zgqysw.cn.gov.cn.zgqysw.cn http://www.morning.xgjhy.cn.gov.cn.xgjhy.cn http://www.morning.skpdg.cn.gov.cn.skpdg.cn http://www.morning.lsgsn.cn.gov.cn.lsgsn.cn http://www.morning.rkfh.cn.gov.cn.rkfh.cn http://www.morning.lhxrn.cn.gov.cn.lhxrn.cn http://www.morning.jydky.cn.gov.cn.jydky.cn http://www.morning.xwbld.cn.gov.cn.xwbld.cn http://www.morning.bpncd.cn.gov.cn.bpncd.cn http://www.morning.nrzbq.cn.gov.cn.nrzbq.cn http://www.morning.fqyqm.cn.gov.cn.fqyqm.cn http://www.morning.bhdyr.cn.gov.cn.bhdyr.cn http://www.morning.gmnmh.cn.gov.cn.gmnmh.cn http://www.morning.wnjbn.cn.gov.cn.wnjbn.cn http://www.morning.srtw.cn.gov.cn.srtw.cn