1. 程式人生 > >Spring Boot教程十:資料庫讀寫分離

Spring Boot教程十:資料庫讀寫分離

文章實現一主兩從的續寫分離:工程目錄


首先pom檔案

<!--排除預設日誌框架-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
      <exclusion>
         <groupId>org.springframework.boot</groupId>
         <
artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope>
</dependency> <!--log4j--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency> <!--druid--> <dependency> <groupId
>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <!-- 資料庫驅動 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> </dependency> <!--mybaits --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency> <!-- 新增redis支援 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.2.8.RELEASE</version> </dependency> <!-- gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <!-- 熱部署外掛修改了程式碼會自動重啟 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <optional>true</optional> </dependency> <!--Aspect--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.12.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.12.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency>

application.properties配置檔案

#根據環境讀取配置檔案
#application-test.properties:測試環境
#application-dev.properties:開發環境
#application-prod.properties:生產環境
spring.profiles.active=dev
#資料來源型別
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#從庫資料來源個數
spring.datasource.readSize=2
# Mybatis配置
spring.mybatis.configLocation=mybatis/mybatis.xml
# mapper路徑
spring.mybatis.mapperLocations=mapper/*.xml

application-dev.properties配置檔案

# Server settings (ServerProperties)
#埠
server.port=8082
server.address=127.0.0.1
#server.sessionTimeout=30
#訪問路徑名稱
server.contextPath=/test
#關閉springboot自帶的ioc
spring.mvc.favicon.enabled = false
#主資料來源配置
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username=root
spring.datasource.password=123456
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置獲取連線等待超時的時間
spring.datasource.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 開啟PSCache,並且指定每個連線上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆
spring.datasource.filters=stat,wall,logback
# 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合併多個DruidDataSource的監控資料
spring.datasource.useGlobalDataSourceStat=true
# 從資料來源
#spring.slave.type=com.alibaba.druid.pool.DruidDataSource
spring.slave.driverClassName=com.mysql.jdbc.Driver
spring.slave.url=jdbc:mysql://127.0.0.1:3306/webdb
spring.slave.username=root
spring.slave.password=123456
spring.slave.initialSize=5
spring.slave.minIdle=5
spring.slave.maxActive=20
# 配置獲取連線等待超時的時間
spring.slave.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.slave.timeBetweenEvictionRunsMillis=60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
spring.slave.minEvictableIdleTimeMillis=300000
spring.slave.validationQuery=SELECT 1 FROM DUAL
spring.slave.testWhileIdle=true
spring.slave.testOnBorrow=false
spring.slave.testOnReturn=false
# 開啟PSCache,並且指定每個連線上PSCache的大小
spring.slave.poolPreparedStatements=true
spring.slave.maxPoolPreparedStatementPerConnectionSize=20
# 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆
spring.slave.filters=stat,wall,logback
# 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
spring.slave.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合併多個DruidDataSource的監控資料
spring.slave.useGlobalDataSourceStat=true
#spring.read2.type=com.alibaba.druid.pool.DruidDataSource
spring.read2.driverClassName=com.mysql.jdbc.Driver
spring.read2.url=jdbc:mysql://127.0.0.1:3306/jfinal
spring.read2.username=root
spring.read2.password=123456
spring.read2.initialSize=5
spring.read2.minIdle=5
spring.read2.maxActive=20
# 配置獲取連線等待超時的時間
spring.read2.maxWait=60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒
spring.read2.timeBetweenEvictionRunsMillis=60000
# 配置一個連線在池中最小生存的時間,單位是毫秒
spring.read2.minEvictableIdleTimeMillis=300000
spring.read2.validationQuery=SELECT 1 FROM DUAL
spring.read2.testWhileIdle=true
spring.read2.testOnBorrow=false
spring.read2.testOnReturn=false
# 開啟PSCache,並且指定每個連線上PSCache的大小
spring.read2.poolPreparedStatements=true
spring.read2.maxPoolPreparedStatementPerConnectionSize=20
# 配置監控統計攔截的filters,去掉後監控介面sql無法統計,'wall'用於防火牆
spring.read2.filters=stat,wall,logback
# 通過connectProperties屬性來開啟mergeSql功能;慢SQL記錄
spring.read2.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合併多個DruidDataSource的監控資料
spring.read2.useGlobalDataSourceStat=true

攔截器配置:

@Aspect
@Order(-1)// 保證該AOP在@Transactional之前執行
@Component
public class DataSourceAop {
    private static Logger logger = Logger.getLogger(DataSourceAop.class);
    @Before("execution(* com.ganinfo.*.mapper..*.select*(..)) || execution(* com.ganinfo.*.mapper..*.get*(..))|| execution(* com.ganinfo.*.mapper..*.query*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.read();
        logger.info("dataSource 切換到:Read");
    }

    @Before("execution(* com.ganinfo.*.mapper..*.insert*(..)) || execution(* com.ganinfo.*.mapper..*.update*(..))")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.write();
        logger.info("dataSource 切換到:Write");
    }

}

public class DataSourceContextHolder {
    private static  Logger logger = Logger.getLogger(DataSourceContextHolder.class);
    private static final ThreadLocal<String> local = new ThreadLocal<String>();

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

    /**
     * 讀可能是多個庫
     */
public static void read() {
        logger.debug("讀操作-----");
        local.set(DataSourceType.read.getType());
    }

    /**
     * 寫只有一個庫
     */
public static void write() {
        logger.debug("寫操作-----");
        local.set(DataSourceType.write.getType());
    }

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

@Configuration
@EnableTransactionManagement
public class DataSourceTransactionManager extends DataSourceTransactionManagerAutoConfiguration {
    private static Logger logger = Logger.getLogger(DataSourceContextHolder.class);
    /**
     * 自定義事務
     * MyBatis自動參與到spring事務管理中,無需額外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的資料來源與DataSourceTransactionManager引用的資料來源一致即可,否則事務管理會不起作用。
     * @return
*/
@Resource(name = "writeDataSource")
    private DataSource dataSource;

    @Bean(name = "transactionManager")
    public org.springframework.jdbc.datasource.DataSourceTransactionManager transactionManagers() {
        logger.info("-------------------- transactionManager init ---------------------");
        return new org.springframework.jdbc.datasource.DataSourceTransactionManager(dataSource);
    }
}
/**
 * @author Shuyu.Wang
 * @package:com.ganinfo.datasource
* @className:MyAbstractRoutingDataSource
* @description:多資料來源切換
* @date 2018-01-28 14:47
 **/
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.getJdbcType();
        // 寫
if (typeKey.equals(DataSourceType.write.getType())) {
            return DataSourceType.write.getType();
        }
        // 讀簡單負載均衡
int number = count.getAndAdd(1);
        int lookupKey = number % dataSourceNumber;
        return new Integer(lookupKey);
    }
}
/**
 * @package:com.ganinfo.config
* @className:DataBaseConfiguration
* @description:讀取mybatis.properties配置檔案
* @author Shuyu.Wang
 * @date 2018-01-28 13:48
 **/
@Configuration
//@PropertySource("classpath:mybatis/mybatis.properties")
public class DataBaseConfiguration {
    private Logger logger = Logger.getLogger(DataBaseConfiguration.class);
    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    @Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource writeDataSource() {
        logger.info("-------------------- writeDataSource init ---------------------");
        DataSource dataSource=DataSourceBuilder.create().type(dataSourceType).build();
        return dataSource;
    }
    /**
     * 有多少個從庫就要配置多少個
     * @return
*/
@Bean(name = "readDataSource1")
    @ConfigurationProperties(prefix = "spring.slave")
    public DataSource readDataSourceOne(){
        logger.info("-------------------- readDataSourceOne init --------------------");
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(name = "readDataSource2")
    @ConfigurationProperties(prefix = "spring.read2")
    public DataSource readDataSourceTwo() {
        logger.info("-------------------- readDataSourceTwo 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;
    }
}
這樣就實現讀寫分離了,業務程式碼正常寫就可以了,程式會自動實現讀寫分離。

程式碼地址:http://download.csdn.net/download/wang_shuyu/10248395