两天搭建了一个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
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 holder = new ThreadLocal();
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 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);
}
}