mysql讀寫分離之springboot集成

springboot、mysql實現讀寫分離

一、首先在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

二、編寫讀庫註解

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](https://my.oschina.net/u/996807) ReadDataSource {  

}

三、增長數據源初始化配置

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;
    }

}

四、增長主從配置常量

/**
 * 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;  
    }  

}

五、事務內讀寫配置

因爲涉及到事務處理,可能會遇到事務中同時用到讀庫和寫庫,可能會有延時形成髒讀,因此增長了線程變量設置,來保證一個事務內讀寫都是同一個庫java

/**
 * 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層,這個得保證攔截器優先級在聲明式事務前面mysql

/**
 * 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)spring

六、增長mybatis相關配置類

mybatis配置sql

@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);
    }
}
相關文章
相關標籤/搜索