参考文章:基于Mybatis框架,采用切面编程方式实现
https://www.jianshu.com/p/2222257f96d3
以上,包括其他技术文章,实现方式都是大同小异,都存在着一些小问题。譬如:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.42version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.42version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatisplus-spring-boot-starterartifactId>
<version>1.0.1version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
spring:
datasource:
slave:
url: jdbc:mysql://192.168.1.70:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: kmob
password: kmob0724(
filters: mergeStat
on-off: true
driver-class-name: com.mysql.jdbc.Driver
master:
url: jdbc:mysql://**.**.**.**:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: **
password: ****
filters: mergeStat
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
mapper-locations: classpath*:com/example/demo/**/mapping/*.xml
typeAliasesPackage: com.example.demo.entity
typeHandlersPackage: com.example.demo.entity.config
global-config:
id-type: 3
db-column-underline: false
refresh-mapper: true
is-capital-mode: false
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
lazyLoadingEnabled: true
jdbcTypeForNull: null
multipleResultSetsEnabled: true
on-off用于配置是否启动多数据源。
package com.example.demo.config;
import java.sql.SQLException;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 数据库数据源配置
* 说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可
*
*/
@Component
@ConfigurationProperties(prefix = "spring.datasource.slave")
public class DruidProperties {
private String url = "jdbc:mysql://127.0.0.1:3306/operation?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
private String username = "root";
private String password = "admin";
private String driverClassName = "com.mysql.jdbc.Driver";
private Integer initialSize = 2;
private Integer minIdle = 1;
private Integer maxActive = 20;
private Integer maxWait = 60000;
private Integer timeBetweenEvictionRunsMillis = 60000;
private Integer minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 'x' from dual";
private Boolean testWhileIdle = true;
private Boolean testOnBorrow = false;
private Boolean testOnReturn = false;
private Boolean poolPreparedStatements = true;
private Integer maxPoolPreparedStatementPerConnectionSize = 20;
private String filters = "stat";
private Boolean onOff = false;
public void coinfig(DruidDataSource dataSource) {
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize); //定义初始连接数
dataSource.setMinIdle(minIdle); //最小空闲
dataSource.setMaxActive(maxActive); //定义最大连接数
dataSource.setMaxWait(maxWait); //最长等待时间
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn);
// 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
public Integer getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public Integer getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public Boolean getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(Boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public Boolean getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public Boolean getPoolPreparedStatements() {
return poolPreparedStatements;
}
public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}
public Integer getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
}
public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
public Boolean getOnOff() {
return onOff;
}
public void setOnOff(Boolean onOff) {
this.onOff = onOff;
}
}
package com.example.demo.dynamic;
public enum DatabaseType {
master("write"),slave("read");
private DatabaseType(String name) {
this.name = name();
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.example.demo.dynamic;
public class DatabaseContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
public static void setDatabaseType(DatabaseType type) {
contextHolder.set(type);
}
public static DatabaseType getDatabaseType() {
return contextHolder.get();
}
}
package com.example.demo.dynamic;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DatabaseType type = DatabaseContextHolder.getDatabaseType();
if(type == null) {
logger.info("========= dataSource ==========" + DatabaseType.slave.name());
return DatabaseType.slave.name();
}
logger.info("========= dataSource ==========" + type);
return type;
}
}
package com.example.demo.dynamic;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }) })
public class DatabasePlugin implements Interceptor {
protected static final Logger logger = LoggerFactory.getLogger(DatabasePlugin.class);
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
private static final Map cacheMap = new ConcurrentHashMap<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
if(!synchronizationActive) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0];
DatabaseType databaseType = null;
if((databaseType = cacheMap.get(ms.getId())) == null) {
//读方法
if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//!selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法,使用主库
if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
databaseType = DatabaseType.master;
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
if(sql.matches(REGEX)) {
databaseType = DatabaseType.master;
} else {
databaseType = DatabaseType.slave;
}
}
}else{
databaseType = DatabaseType.master;
}
logger.warn("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(), databaseType.name(), ms.getSqlCommandType().name());
cacheMap.put(ms.getId(), databaseType);
}
DatabaseContextHolder.setDatabaseType(databaseType);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
通过正则表达式,拦截sql语句匹配类型设置数据源。用map对象缓存数据,数据量大的话,此处需要优化。
package com.example.demo.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.enums.DBType;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.example.demo.dynamic.DatabasePlugin;
import com.example.demo.dynamic.DatabaseType;
import com.example.demo.dynamic.DynamicDataSource;
/**
* MybatisPlus 配置
*
*
* @author fengjk
* @date 2017年10月17日
* @since 1.0
*/
@Configuration
@MapperScan(basePackages = {"com.example.demo.dao"})
public class MybatisPlusConfig {
@Autowired
DruidProperties druidProperties;
/**
* mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setDialectType(DBType.MYSQL.getDb());
return paginationInterceptor;
}
@Bean(name = "masterDataSource")
@Qualifier("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@Qualifier("slaveDataSource")
public DataSource slaveDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.coinfig(dataSource);
return dataSource;
}
/**
* 构造多数据源连接池
* Master 数据源连接池采用 HikariDataSource
* Slave 数据源连接池采用 DruidDataSource
* @param master
* @param slave
* @return
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map
如果是用Mybatis框架,把MybatisSqlSessionFactoryBean修改成SqlSessionFactoryBean就行,具体参考前言提到的文章。
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
UserMapper userMapper;
@PostMapping("/insert")
@ResponseBody
public JSONResult insert(User user){
user.setId(IdWorker.getId());
userMapper.insert(user);
return JSONResult.ok(user);
}
@PostMapping("/update")
@ResponseBody
public JSONResult update(User user){
userMapper.updateById(user);
return JSONResult.ok(user);
}
@PostMapping("/delete")
@ResponseBody
public JSONResult delete(long id){
userMapper.deleteById(id);
return JSONResult.ok(id);
}
@GetMapping("/select")
@ResponseBody
public JSONResult select(){
List userLists = userMapper.selectList(new EntityWrapper());
return JSONResult.ok(userLists);
}
@GetMapping("/selectCount")
@ResponseBody
public JSONResult selectCount(){
int count = userMapper.selectCount(new EntityWrapper());
return JSONResult.ok(count);
}
}
其他类就不晒代码了,自行下载代码。
大部分细节代码都没做讲解,有些东西讲多了真没意思,还是要靠自己去理解(参考上文链接文章)。最后,文章如有笔误,请留言相告,谢谢。欢迎加Q群交流学习:583138104
代码下载