mysql读写分离之springboot集成

springboot、mysql实现读写分离1、首先在springcloudconfig中配置读写数据库mysql:datasource:readSize:1#读库个数type:com.alibaba.druid.pool.DruidDataSourcewrite:url:jdbc:mysql://200…

大家好,又见面了,我是你们的朋友全栈君。

springboot、mysql实现读写分离

1、首先在springcloud config中配置读写数据库

mysql:  
  datasource:  
    readSize: 1  #读库个数  
    type: com.alibaba.druid.pool.DruidDataSource 
    write:  
       url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false 
       username: root  
       password: 123123  
       driver-class-name: com.mysql.cj.jdbc.Driver
       minIdle: 5  
       maxActive: 100  
       initialSize: 10  
       maxWait: 60000  
       timeBetweenEvictionRunsMillis: 60000  
       minEvictableIdleTimeMillis: 300000  
       validationQuery: select 'x'  
       testWhileIdle: true  
       testOnBorrow: false  
       testOnReturn: false  
       poolPreparedStatements: true  
       maxPoolPreparedStatementPerConnectionSize: 50  
       removeAbandoned: true  
       filters: stat  
    read01:  
       url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false
       username: root  
       password: 123123  
       driver-class-name: com.mysql.cj.jdbc.Driver
       minIdle: 5  
       maxActive: 100  
       initialSize: 10  
       maxWait: 60000  
       timeBetweenEvictionRunsMillis: 60000  
       minEvictableIdleTimeMillis: 300000  
       validationQuery: select 'x'  
       testWhileIdle: true  
       testOnBorrow: false  
       testOnReturn: false  
       poolPreparedStatements: true  
       maxPoolPreparedStatementPerConnectionSize: 50  
       removeAbandoned: true  
       filters: stat  
    read02:  
       url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false
       username: root  
       password: 123123  
       driver-class-name: com.mysql.cj.jdbc.Driver
       minIdle: 5  
       maxActive: 100  
       initialSize: 10  
       maxWait: 60000  
       timeBetweenEvictionRunsMillis: 60000  
       minEvictableIdleTimeMillis: 300000  
       validationQuery: select 'x'  
       testWhileIdle: true  
       testOnBorrow: false  
       testOnReturn: false  
       poolPreparedStatements: true  
       maxPoolPreparedStatementPerConnectionSize: 50  
       removeAbandoned: true  
       filters: stat  

2、编写读库注解

import java.lang.annotation.Documented;  
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  

@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
@Documented  
public @interface ReadDataSource {  

} 

3、增加数据源初始化配置

import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * name:DataSourceConfiguration  
 * <p></p>    
 * @author:lipeng    
 * @data:2018年6月27日 下午5:55:35      
 * @version  1.0
 */
@Configuration  
public class DataSourceConfiguration {  

    private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);  

    @Value("${mysql.datasource.type}")  
    private Class<? extends DataSource> dataSourceType;  

    /** 
     * 写库 数据源配置 
     * @return 
     */  
    @Bean(name = "writeDataSource")  
    @Primary  
    @ConfigurationProperties(prefix = "mysql.datasource.write")  
    public DataSource writeDataSource() {  
        log.info("-------------------- writeDataSource init ---------------------");  
        return DataSourceBuilder.create().type(dataSourceType).build();  
    }  

    /** 
     * 有多少个从库就要配置多少个 
     * @return 
     */  
    @Bean(name = "readDataSource01")  
    @ConfigurationProperties(prefix = "mysql.datasource.read01")  
    public DataSource readDataSourceOne() {  
        log.info("-------------------- read01 DataSourceOne init ---------------------");  
        return DataSourceBuilder.create().type(dataSourceType).build();  
    }


    @Bean(name = "readDataSource02")  
    @ConfigurationProperties(prefix = "mysql.datasource.read02")  
    public DataSource readDataSourceTwo() {  
        log.info("-------------------- read01 DataSourceOne init ---------------------");  
        return DataSourceBuilder.create().type(dataSourceType).build();  
    }

    @Bean("readDataSources")
    public List<DataSource> readDataSources(){
        List<DataSource> dataSources=new ArrayList<>();
        dataSources.add(readDataSourceOne());
        dataSources.add(readDataSourceTwo());
        return dataSources;
    }

}  

4、增加主从配置常量

/**
 * name:DataSourceType  
 * <p></p>    
 * @author:lipeng    
 * @data:2018年6月28日 上午9:25:44      
 * @version  1.0
 */
public enum DataSourceType {  

    read("read", "从库"),  
    write("write", "主库");  

    private String type;  

    private String name;  

    DataSourceType(String type, String name) {  
        this.type = type;  
        this.name = name;  
    }  

    public String getType() {  
        return type;  
    }  

    public void setType(String type) {  
        this.type = type;  
    }  

    public String getName() {  
        return name;  
    }  

    public void setName(String name) {  
        this.name = name;  
    }  

}  

5、事务内读写配置

由于涉及到事务处理,可能会遇到事务中同时用到读库和写库,可能会有延时造成脏读,所以增加了线程变量设置,来保证一个事务内读写都是同一个库

/**
 * name:DataSourceContextHolder  
 * <p></p>    
 * @author:lipeng    
 * @data:2018年6月27日 下午5:57:39      
 * @version  1.0
 */
public class DataSourceContextHolder {  

    private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);  

    //线程本地环境  
    private static final ThreadLocal<String> local = new ThreadLocal<String>();  

    public static ThreadLocal<String> getLocal() {  
        return local;  
    }  

    /** 
     * 读库 
     */  
    public static void setRead() {  
        local.set(DataSourceType.read.getType());  
        log.info("数据库切换到读库...");  
    }  

    /** 
     * 写库 
     */  
    public static void setWrite() {  
        local.set(DataSourceType.write.getType());  
        log.info("数据库切换到写库...");  
    }  

    public static String getReadOrWrite() {  
        return local.get();  
    }  

    public static void clear(){  
        local.remove();  
    }  
}  

如果在注解在service层并且声明式事务也在service层,这个得保证拦截器优先级在声明式事务前面

/**
 * name:DataSourceAopInService  
 * 在service层觉得数据源 
 * 必须在事务AOP之前执行,所以实现Ordered,order的值越小,越先执行 
 * 如果一旦开始切换到写库,则之后的读都会走写库 
 *     
 * @author:lipeng    
 * @data:2018年6月27日 下午5:59:17      
 * @version  1.0
 */
@Aspect  
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)  
@Component  
public class DataSourceAopInService implements PriorityOrdered{  

private static Logger log = LoggerFactory.getLogger(DataSourceAopInService.class);  


    @Before("@annotation(com.sangfor.quote.datasource.annotation.ReadDataSource) ")  
    public void setReadDataSourceType() {  
        //如果已经开启写事务了,那之后的所有读都从写库读  
        if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){  
            DataSourceContextHolder.setRead();  
        }  

    }  

    @Before("@annotation(com.sangfor.quote.datasource.annotation.WriteDataSource) ")  
    public void setWriteDataSourceType() {  
        DataSourceContextHolder.setWrite();  
    }  

    @Override  
    public int getOrder() {  
        /** 
         * 值越小,越优先执行 
         * 要优于事务的执行 
         * 在启动类中加上了@EnableTransactionManagement(order = 10)  
         */  
        return 1;  
    }  

} 

并且在启动类或者配置类中增加注解order配置
@EnableTransactionManagement(order = 10)

6、增加mybatis相关配置类

mybatis配置

@Configuration
@AutoConfigureAfter(DataSourceConfiguration.class)
@MapperScan(basePackages = "com.sangfor.springboot")
public class MybatisConfiguration {

    private static Logger log = LoggerFactory.getLogger(MybatisConfiguration.class);

    @Value("${mysql.datasource.readSize}")
    private String readDataSourceSize;
    @Autowired
    @Qualifier("writeDataSource")
    private DataSource writeDataSource;
    @Autowired
    @Qualifier("readDataSources")
    private List<DataSource> readDataSources;

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
        sqlSessionFactoryBean.setTypeAliasesPackage("com.sangfor.quote.model");
         //设置mapper.xml文件所在位置   
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml");  
        sqlSessionFactoryBean.setMapperLocations(resources);  
        sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 有多少个数据源就要配置多少个bean
     * 
     * @return
     */
    @Bean
    public AbstractRoutingDataSource roundRobinDataSouceProxy() {
        int size = Integer.parseInt(readDataSourceSize);
        MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource");
        // 写
        targetDataSources.put(DataSourceType.write.getType(), writeDataSource);
        // targetDataSources.put(DataSourceType.read.getType(),readDataSource);
        // 多个读数据库时
        for (int i = 0; i < size; i++) {
            targetDataSources.put(i, readDataSources.get(i));
        }
        proxy.setDefaultTargetDataSource(writeDataSource);
        proxy.setTargetDataSources(targetDataSources);
        return proxy;
    }

}

多数据源切换

/**
 * 多数据源切换
 * name:MyAbstractRoutingDataSource  
 * <p></p>    
 * @author:lipeng    
 * @data:2018年6月27日 下午6:57:34      
 * @version  1.0
 */
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
    private final int dataSourceNumber;
    private AtomicInteger count = new AtomicInteger(0);

    public MyAbstractRoutingDataSource(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String typeKey = DataSourceContextHolder.getReadOrWrite();
        if(StringUtils.isBlank(typeKey)||typeKey.equals(DataSourceType.write.getType())) {
            return DataSourceType.write.getType();
        }
        // 读 简单负载均衡
        int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}

事务管理配置

@Configuration
@EnableTransactionManagement(order = 10)
@Slf4j
@AutoConfigureAfter({ MybatisConfiguration.class })
public class TransactionConfiguration extends DataSourceTransactionManagerAutoConfiguration {

    @Bean
    @Autowired
    public DataSourceTransactionManager transactionManager(MyAbstractRoutingDataSource roundRobinDataSouceProxy) {
        log.info("事物配置");
        return new DataSourceTransactionManager(roundRobinDataSouceProxy);
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/127034.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 20考研 | 2020考研全程规划,19上岸复旦学长。各科各阶段复习规划。

    20考研 | 2020考研全程规划,19上岸复旦学长。各科各阶段复习规划。下面我在分享一下我之前写过的一篇文章高能干货预警文章目前30142字,这可能是最负责的一篇文章了。文章很长,建议拿好笔记慢慢看。本文会解决你在考研各科在不同时期不同阶段遇到的所有问题,方法具体到草稿纸怎么使用,课本具体怎么使用,相信我,读完你一定会有巨大收获。我总结了我一年以来遇到的所有问题,使用的所有方法。既然最终目的是在考研这场「考试」中获得高分,那么所有的时间和精力,都应该围绕…

    2022年9月24日
    3
  • 串口转tcp软件_tcp转虚拟串口软件

    串口转tcp软件_tcp转虚拟串口软件由于项目调试需求,代码在远程服务器的虚拟机上,在本地计算机通过串口连接需要对接的设备,在远程服务器的上位机程序需要和此设备进行对接,系统结构如图1所示。图1系统网络结构图如何将本地的串口共享给远程服务器上的虚拟机里面的上位机程序呢?远程服务器开启了内网穿透,使用的SAKURAFRP,感兴趣的可以自己摸索下,很好用的。我直接穿透的远程桌面,而远程桌面可以将本地计算机的串口共享到远程服务器,如图2所示:图2通过远程桌面共享端口假设本地计算机的串口如图3所示,经过远程桌面共享后在远程

    2025年9月16日
    5
  • AdventureWorks2008 数据库安装

    AdventureWorks2008 数据库安装我使用的操作系统是win8.1,由于对早前安装的sqlserver2008的兼容性不太好,要安装对应的servicepack来解决一下这个问题。如何使用SQLServer在Windo

    2022年7月3日
    26
  • 即时通讯源代码,im源码功能全套[通俗易懂]

    即时通讯源代码,im源码功能全套[通俗易懂]即时通讯源代码,im源码,im源代码即时通讯全套源码语言:C/C++语言编写,VC++开发平台产品包含完善的即时通讯系统,是一套集即时通讯和视频会议为一体的企业级办公协同交流产品,可以广泛应用于政府、企业、教育机构和公司等客户端:大致功能(具体以产品演示为准):在线文字聊天离线留言历史消息记录查询 公告消息提醒语音聊天 视频聊天群发留言 输入状态感知个…

    2022年5月14日
    248
  • Environment类,获取程序所在机器信息

    Environment类,获取程序所在机器信息

    2021年9月17日
    202
  • 国产数据库乱象_四代户户通怎么开户

    国产数据库乱象_四代户户通怎么开户其实这篇文章是我周末开始写的,写这篇文章的这个周末,我的很多时候都是在思考一个数据库国产化替代的建设方案,翻阅了大量的资料。今年正好是我参加工作后的第31个年头,工作的最初十年,我写了十年代码,从汇编、COBOL到C语言,写了几十万行代码;随后的十几年,我一直在帮助用户用好数据库,也在帮助Oracle推广RAC技术;2015年开始,我一边继续从事数据库优化的工作,一边在帮助客户如何从Oracle迁移到成本更低的数据库系统上。所以对国产数据库我一直有一种十分特殊的情感,这是一种爱恨交织的情感。所以今天最后用“

    2026年1月27日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号