龙空技术网

SpringBoot+Aop+MyBatis读写分离

西西文 227

前言:

而今看官们对“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);    }}

标签: #springboot mybatis读写分离