前言:
而今看官们对“springboot mybatis读写分离”大体比较注意,朋友们都想要知道一些“springboot mybatis读写分离”的相关内容。那么小编也在网上收集了一些关于“springboot mybatis读写分离””的相关内容,希望同学们能喜欢,姐妹们快快来学习一下吧!引言
读写分离通俗一点而言:执行的SQL选择哪个数据源
依靠中间件
常见的中间件:
CobarMyCATAtlasTDDLSharding Sphere
以上的中间件感兴趣的小伙伴;可以去找
程序来做分离;主要使用的spring提供的路由数据源,以及AOP
Springboot Aop代理的形式无缝切换数据源的形式
功能实现
数据源的配置(druid)
spring: master: url: jdbc:mysql://192.168.3.88:3306/article2?useUnicode=true&characterEncoding=utf8&useSSL=false&tinyInt1isBit=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: bNVOqb7WKLX5Bjnw+LMv92taj25KOxDimXxILPQjw42wgv+1lHzOH8kr97xDwWdhpY67QuYCS7sWN4W46YbkFA== password: gPqQO7ufTipMCCQeDYphcOMOWqX8XvOCrFgzFjdPfweb5J1D1Q9ov8t4+aVn5txu0lxHrU9R0zeoNTe+Ab11+A== initialSize: 5 minIdle: 5 maxActive: 20 cluster: url: jdbc:mysql://192.168.3.99:3306/article1?useUnicode=true&characterEncoding=utf8&useSSL=false&tinyInt1isBit=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: bNVOqb7WKLX5Bjnw+LMv92taj25KOxDimXxILPQjw42wgv+1lHzOH8kr97xDwWdhpY67QuYCS7sWN4W46YbkFA== password: gPqQO7ufTipMCCQeDYphcOMOWqX8XvOCrFgzFjdPfweb5J1D1Q9ov8t4+aVn5txu0lxHrU9R0zeoNTe+Ab11+A== initialSize: 5 minIdle: 5 maxActive: 20 redis: database: 0 timeout: 10000 # Redis服务器地址 host: 127.0.0.1 # Redis服务器连接端口 port: 6379 # Redis服务器连接密码(默认为空) password: # 连接池最大连接数(使用负值表示没有限制) pool: max-active: 8 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: -1 # 连接池中的最大空闲连接 max-idle: 8 # 连接池中的最小空闲连接 min-idle: 0#mybatismybatis: # 配置映射文件位置 mapper-locations: classpath*:mapper/**/*.xml # 开启驼峰匹配 mapUnderscoreToCamelCase: true
数据源的工厂创建
public class DataSourcePool { // 对应主库 @ConfigurationProperties(prefix = "spring.master") static class MasterDataSource extends BaseDataSourceProperties { } // 对应从数据库 @ConfigurationProperties(prefix = "spring.cluster") static class LcDataSource extends BaseDataSourceProperties { } /** * 创建数据源 * * @param dataSourceProperties * @return * @throws SQLException */ static DataSource create(BaseDataSourceProperties dataSourceProperties) throws SQLException { DecryptDruidDataSource dataSource = new DecryptDruidDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl(dataSourceProperties.getUrl()); dataSource.setUsername(dataSourceProperties.getUsername()); dataSource.setPassword(dataSourceProperties.getPassword()); // 初始化大小,最小,最大 dataSource.setInitialSize(dataSourceProperties.getInitialSize()); dataSource.setMinIdle(dataSourceProperties.getMinIdle()); dataSource.setMaxActive(dataSourceProperties.getMaxActive()); // 配置获取连接等待超时的时间 dataSource.setMaxWait(30000); // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 dataSource.setTimeBetweenEvictionRunsMillis(30000); // 配置一个连接在池中最小生存的时间,单位是毫秒 dataSource.setMinEvictableIdleTimeMillis(300000); dataSource.setValidationQuery("select 1"); dataSource.setTestWhileIdle(true); dataSource.setTestOnBorrow(false); dataSource.setTestOnReturn(false); // 打开PSCache,并且指定每个连接上PSCache的大小 dataSource.setPoolPreparedStatements(true); dataSource.setMaxPoolPreparedStatementPerConnectionSize(20); // 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 dataSource.addFilters("stat,slf4j"); // 通过connectProperties属性来打开mergeSql功能;慢SQL记录 dataSource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000"); return dataSource; }}
Durid组件维护多个数据源
@Slf4j@Configuration@EnableConfigurationProperties({DataSourcePool.MasterDataSource.class, DataSourcePool.LcDataSource.class })public class DruidConfiguration { @Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close") public DataSource mainDataSource(@Autowired DataSourcePool.MasterDataSource mainDataSource) throws SQLException { log.info("写库"); return DataSourcePool.create(mainDataSource); } @Bean(name = "lcDataSource", initMethod = "init", destroyMethod = "close") public DataSource lcDataSource(@Autowired DataSourcePool.LcDataSource lcDataSource) throws SQLException { log.info("读库"); return DataSourcePool.create(lcDataSource); } /** * 写数据源 */ @Bean(name = "dataSource") @Primary public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("lcDataSource") DataSource lcDataSource ) { DynamicDataSource resolver = new DynamicDataSource(); Map<Object, Object> dataSources = new HashMap<>(); dataSources.put("master", masterDataSource); dataSources.put("cluster", lcDataSource); resolver.setTargetDataSources(dataSources); //默认主库 是写库 resolver.setDefaultTargetDataSource(masterDataSource); return resolver; }}
数据库账号密码解密
public class DecryptDruidDataSource extends DruidDataSource{ private static final long serialVersionUID = 1L; public void setUsername(String username) { try { username = ConfigTools.decrypt(username); } catch (Exception e) { e.printStackTrace(); } super.setUsername(username); } public void setPassword(String password) { try { password = ConfigTools.decrypt(password); } catch (Exception e) { e.printStackTrace(); } super.setPassword(password); } public static void main(String[] args) { try { String root = ConfigTools.encrypt("12xxxx!!!"); System.out.println(root); String decrypt = ConfigTools.decrypt(root); System.out.println(decrypt); } catch (Exception e) { e.printStackTrace(); } }}
数据源路由(AbstractRoutingDataSource 调用自己的determineTargetDataSource()方法,该方法中就调用了我们实现的determineCurrentLookupKey()方法得到key然后从resolvedDataSources中按照key取出指定的数据源
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { String type = DynamicDataSourceContextHolder.getDataSourceType(); if (type.equals(DataSourceTypeEnum.MASTER.getName())) { return DataSourceTypeEnum.MASTER.getName(); } return DataSourceTypeEnum.CLUSTER.getName(); }}核心线程维护数据源:DynamicDataSourceContextHolder 这个类是实现动态数据源切换的关键,单例的;采用ThreadLocal一来保证线程的安全性,而来保证同一个线程获取数据源的唯一性。putDataSource(name)方法用于切换数据源,get()方法用于获取数据源,clear()方法用于清空本地线程的所有数据源,让其使用默认的数据源
核心线程维护数据源:DynamicDataSourceContextHolder 这个类是实现动态数据源切换的关键,单例的;采用ThreadLocal一来保证线程的安全性,而来保证同一个线程获取数据源的唯一性。putDataSource(name)方法用于切换数据源,get()方法用于获取数据源,clear()方法用于清空本地线程的所有数据源,让其使用默认的数据源
public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static ThreadLocal<String> getLocal() { return contextHolder; } private DynamicDataSourceContextHolder() {} public static void setMaster() { log.info("写操作-----"); contextHolder.set(DataSourceTypeEnum.MASTER.getName()); } public static void setCluster(){ log.info("读操作-----"); contextHolder.set(DataSourceTypeEnum.CLUSTER.getName()); } public static String getDataSourceType() { return contextHolder.get(); }}
Aop的代理的形式:实现切换数据源
@Aspect@Order(-1)// 保证该AOP在@Transactional之前执行@Component@Slf4jpublic class DataSourceAop { @Before("execution(* com.yyy.xx.activity.dao..*.insert*(..)) || execution(* com.yy.xx.activity.dao..*.update*(..))|| execution(* com.yy.xx.activity.dao..*.delete*(..))") public void setWriteDataSourceType() { DynamicDataSourceContextHolder.setMaster(); log.info("dataSource 切换到:master"); } @Before("execution(* com.yy.xx.activity.dao..*.select*(..)) || execution(* com.yy.xx.activity.dao..*.get*(..))|| execution(* com.yy.xx.activity.dao..*.query*(..))") public void setReadDataSourceType() { DynamicDataSourceContextHolder.setCluster(); log.info("dataSource 切换到:cluster"); }}
写库事务控制
@Configuration@EnableTransactionManagement@Slf4jpublic class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration { /** * 自定义事务 * MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。 * @return */ @Resource(name = "masterDataSource") private DataSource masterDataSource; @Bean(name = "transactionManager") public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManagers() { log.info("-------------------- transactionManager init ---------------------"); return new org.springframework.jdbc.datasource.DataSourceTransactionManager(masterDataSource); }}