兩天搭建了一個SpringBoot,Mybatis多數據源讀寫分離,redis實現session共享的例子。記錄一下~~~
前提條件:Spring Boot,Mybatis 單數據庫能正常運行
一、Spring Boot整合Mybatis實現讀寫分離(後續會做Mysql主從複製)
直接貼代碼了,重要的代碼已經做了註釋,哪兒不合適可以一起探討~~~
1.application.yml(application.properties)
spring:
profiles:
# 使用開發環境
active: dev
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
pool:
max-active: 8
max-wait: -1
max-idel: 8
min-idel: 0
timeout: 0
mybatis:
configuration:
# 駝峯轉換
map-underscore-to-camel-case: true
# 默認緩存數量
default-fetch-size: 100
# SQL執行的超時時間
default-statement-timeout: 3000
# 日誌輸出到控制檯
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.application-dev.yml(mybatis)
write:
datasource:
username: root
password: root@123
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
# 最大連接數
maxActive: 1000
# 連接池初始化大小
initialSize: 100
# 獲取連接的最大等待時間,單位毫秒
maxWait: 60000
# 最小保留數
minIdle: 500
# 檢測關閉空閒連接的間隔時間,單位毫秒
timeBetweenEvictionRunsMillis: 60000
# 連接的最小生存時間,,單位毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxOpenPreparedStatements: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat, wall, slf4j
# 合併多個DruidDataSource的監控數據
useGlobalDataSourceStat: true
read:
datasource:
username: root
password: root@123
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8
# 最大連接數
maxActive: 1000
# 連接池初始化大小
initialSize: 100
# 獲取連接的最大等待時間,單位毫秒
maxWait: 60000
# 最小保留數
minIdle: 500
# 檢測關閉空閒連接的間隔時間,單位毫秒
timeBetweenEvictionRunsMillis: 60000
# 連接的最小生存時間,,單位毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxOpenPreparedStatements: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat, wall, slf4j
# 合併多個DruidDataSource的監控數據
useGlobalDataSourceStat: true
3.DynamicDataSourceConfig.java
package com.springboot.zxf.config.mybatis;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.builder.SpringApplicationBuilder;
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;
import org.springframework.context.annotation.PropertySource;
/**
* 多數據源配置
*/
import com.alibaba.druid.pool.DruidDataSource;
import com.springboot.zxf.config.mybatis.DynamicDataSource;
import com.springboot.zxf.config.mybatis.DynamicDataSourceTransactionManager;
import com.springboot.zxf.constants.DynamicDataSourceGlobal;
@Configuration
@PropertySource(value = { "classpath:application-dev.yml" })
public class DynamicDataSourceConfig {
// 主數據庫配置源
@Bean(name = "writeDataSource", destroyMethod = "close")
@Primary
@ConfigurationProperties(prefix = "write.datasource")
public DataSource writeDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
// 從數據庫配置源
@Bean(name = "readDataSource", destroyMethod = "close")
@ConfigurationProperties(prefix = "read.datasource")
public DataSource readDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
// 直接數據源
@Bean(name = "dataSource")
public DynamicDataSource getDynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DynamicDataSourceGlobal.READ.name(), readDataSource());
dataSourceMap.put(DynamicDataSourceGlobal.WRITE.name(), writeDataSource());
// 傳入數據源map,AbstractRoutingDataSource將以key來分配數據源
//dynamicDataSource.setDefaultTargetDataSource(writeDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
@Bean
public DynamicDataSourceTransactionManager getDynamicDataSourceTransactionManager(
@Qualifier("dataSource") DynamicDataSource dataSource) {
DynamicDataSourceTransactionManager transactionManager = new DynamicDataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DynamicDataSource dataSource) {
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean
public SqlSessionTemplate getSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
return template;
}
protected SpringApplicationBuilder springApplicationBuilder(SpringApplicationBuilder builder) {
return builder.sources(DynamicDataSourceConfig.class);
}
}
4.DynamicDataSource.java
package com.springboot.zxf.config.mybatis;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.springboot.zxf.constants.DynamicDataSourceGlobal;
public class DynamicDataSource extends AbstractRoutingDataSource {
// 返回分配的數據庫的key
@Override
protected Object determineCurrentLookupKey() {
DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
if (dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE) {
return DynamicDataSourceGlobal.WRITE.name();
}
return DynamicDataSourceGlobal.READ.name();
}
}
5.DynamicDataSourceHolder.java
package com.springboot.zxf.config.mybatis;
import com.springboot.zxf.constants.DynamicDataSourceGlobal;
public final class DynamicDataSourceHolder {
private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>();
private DynamicDataSourceHolder() {
}
public static void putDataSource(DynamicDataSourceGlobal dataSource) {
holder.set(dataSource);
}
public static DynamicDataSourceGlobal getDataSource() {
return holder.get();
}
public static void clearDataSource() {
holder.remove();
}
}
6.DynamicDataSourceTransactionManager.java
根據事務是否是隻讀來設置數據源
package com.springboot.zxf.config.mybatis;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import com.springboot.zxf.constants.DynamicDataSourceGlobal;
/**
* 根據事務是否可讀
* @author zhaoxuefeng
*
*/
@SuppressWarnings("serial")
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
//只讀事務到讀庫,讀寫事務到寫庫
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//設置數據源
boolean readOnly = definition.isReadOnly();
if (readOnly) {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
} else {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
}
super.doBegin(transaction, definition);
}
//清理本地線程的數據源
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
DynamicDataSourceHolder.clearDataSource();
}
}
7.DynamicDataSourceGlobel.java
package com.springboot.zxf.constants;
public enum DynamicDataSourceGlobal {
READ, WRITE;
}
8.使用,這種方法使用事務是否只讀進行條件判斷的。當然還可以有其他方式比如SpringAop
package com.springboot.zxf.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.springboot.zxf.mapper.UserMapper;
import com.springboot.zxf.model.User;
import com.springboot.zxf.service.UserService;
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Resource
private UserMapper userMapper;
@Override
@Transactional(readOnly = true)
public List<User> queryAllUser(){
return userMapper.queryAllUser();
}
@Override
public long save(User user) {
return userMapper.insert(user);
}
}
9.測試,自己創建數據庫並配置mapper
在瀏覽器中輸入:localhost:8080/user/list 和localhost:8080/user/save 可以看到鏈接不同的數據庫
package com.springboot.zxf.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.github.pagehelper.PageHelper;
import com.springboot.zxf.model.User;
import com.springboot.zxf.service.UserService;
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/list")
public Object list() {
PageHelper.startPage(1, 100);
return userService.queryAllUser();
}
@RequestMapping(value = "/save")
public Object save() {
User user = new User();
user.setName("123");
user.setPassword("123");
user.setEmail("[email protected]");
return userService.save(user);
}
}