Spring 数据库读写分离
读写分离常见有俩种方式
1 第一种方式比较常用就是定义2个数据库连接,一个是Master,另一个是Slave。更新数据时我们取Master,查询数据时取Slave。太过简单不做介绍。
2 第二种方数据源式动态切换,将数据源动态织入到程序中,达到动态选择读取主库还是从库的目的。主要使用的技术是:annotation,Spring AOP ,反射。下面介绍这种方式
首先创建DatabaseConfiguration
package com.testdatasource.third.configuration.datasource; import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.mapper.LogicSqlInjector;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import com.testdatasource.common.enums.DatasourceType;
import com.testdatasource.third.configuration.properties.DruidProperties;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; @Configuration
@EnableAutoConfiguration
public class DatabaseConfiguration {
@Autowired
private ApplicationContext appContext; @Autowired
private DruidProperties druidProperties; @Bean(name = "readDatasource")
@Primary
@ConfigurationProperties(prefix = "read.datasource")
public DataSource readDatasource() {
return getReadDruidDataSource();
} /**
* 写入数据源连接池配置
*/
@Bean(name = "writeDatasource")
@ConfigurationProperties(prefix = "write.datasource")
public DataSource writeDatasource() {
return getWriteDruidDataSource();
} private DruidDataSource getReadDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.configR(dataSource);
return dataSource;
} private DruidDataSource getWriteDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.configW(dataSource);
return dataSource;
} //初始化数据源bean,这个bean很重要,后面事务控制也会用到
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) {
DynamicDataSource proxy = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put( DatasourceType.read.getName(), read);
targetDataSources.put(DatasourceType.write.getName(), write);
proxy.setDefaultTargetDataSource(read);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
//初始化SqlSessionFactory,将自定义的多数据源ReadWriteSplitRoutingDataSource类实例注入到工厂中
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("readDatasource")DataSource read, @Qualifier("writeDatasource") DataSource write) throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory. setDataSource(this.roundRobinDataSouceProxy(read, write));
sqlSessionFactory.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*Mapper.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setGlobalConfig(globalConfiguration());
return sqlSessionFactory.getObject();
} @Bean
public GlobalConfiguration globalConfiguration() {
GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
conf.setLogicDeleteValue("-1");
conf.setLogicNotDeleteValue("1");
conf.setIdType(0);
conf.setDbColumnUnderline(true);
conf.setRefresh(true);
return conf;
}
}
创建 DataSourceContextHolder
package com.testdatasource.third.configuration.datasource; import com.testdatasource.common.enums.DatasourceType; /**
* ClassName:DataSourceContextHolder
*
* @Description : 当前线程数据源
*/
public class DataSourceContextHolder {
/**
* 默认数据源
*/
public static final String DEFAULT_DS = DatasourceType.read.getName(); private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); // 设置数据源名
public static void setDB(String dbType) {
contextHolder.set(dbType);
} // 获取数据源名
public static String getDB() {
return (contextHolder.get());
} // 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
创建 DynamicDataSource
package com.testdatasource.third.configuration.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /**
* ClassName:DynamicDataSource
*
* @Description : 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDB();
}
}
创建 MybatisPlusConfig
package com.testdatasource.third.configuration.datasource; import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* ClassName:MybatisPlusConfig
* @Description : mybatis分页插件
* @version
*/
@Configuration
public class MybatisPlusConfig { /**
* mybatis-plus分页插件<br>
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
return paginationInterceptor;
}
}
创建 MyDataSourceTransactionManagerAutoConfiguration
package com.testdatasource.third.configuration.datasource; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; /**
*
* 多数据源事物
*/
@Configuration
@EnableTransactionManagement
public class MyDataSourceTransactionManagerAutoConfiguration extends DataSourceTransactionManagerAutoConfiguration {
@Autowired
private ApplicationContext appContext;
/**
* 自定义事务
* MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
* @return
*/
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManagers() {
return new DataSourceTransactionManager((DataSource)appContext.getBean("roundRobinDataSouceProxy"));
}
}
数据源枚举类
package com.testdatasource.common.enums; /**
* ClassName:DatasourceType
*
* @Description :数据库读写类型
* @version
*/
public enum DatasourceType {
write("write"), read("read"); private String name; private DatasourceType(String name) {
this.name = name();
} public String getName() {
return this.name;
} public void setName(String name) {
this.name = name;
}
}
数据源配置类
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import java.sql.SQLException; /**
* ClassName:DruidProperties
*
* @Description :
* druid连接池配置文件属性,说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可
* </p>
*/
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties { @Value("${spring.datasource.read_url}")
private String rurl;
@Value("${spring.datasource.read_username}")
private String rusername;
@Value("${spring.datasource.read_password}")
private String rpassword;
@Value("${spring.datasource.write_url}")
private String wurl;
@Value("${spring.datasource.write_username}")
private String wusername;
@Value("${spring.datasource.write_password}")
private String wpassword; 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 1 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"; public void configR(DruidDataSource dataSource) { dataSource.setUrl(rurl);
dataSource.setUsername(rusername);
// dataSource.setPassword(AESUtil.decrypt(rpassword, null));
dataSource.setPassword(AESUtils.AESDecode(null,rpassword)); 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 void configW(DruidDataSource dataSource) { dataSource.setUrl(wurl);
dataSource.setUsername(wusername);
dataSource.setPassword(AESUtils.AESDecode(null,wpassword)); 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 getRurl() {
return rurl;
} public void setRurl(String rurl) {
this.rurl = rurl;
} public String getRusername() {
return rusername;
} public void setRusername(String rusername) {
this.rusername = rusername;
} public String getRpassword() {
return rpassword;
} public void setRpassword(String rpassword) {
this.rpassword = rpassword;
} public String getWurl() {
return wurl;
} public void setWurl(String wurl) {
this.wurl = wurl;
} public String getWusername() {
return wusername;
} public void setWusername(String wusername) {
this.wusername = wusername;
} public String getWpassword() {
return wpassword;
} public void setWpassword(String wpassword) {
this.wpassword = wpassword;
} 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;
}
}
annotation类作用于强制写库或者读库
package com.testdatasource.common.annotation; import com.testdatasource.common.enums.DatasourceType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; /**
* ClassName:DS
* @Description :自定义切换数据源注解
* @version
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
DatasourceType value() default DatasourceType.write;
}
切面类DynamicDataSourceAspect 用于辅助自定义切面注解切换数据源
package com.testdatasource.third.aspect; import com.testdatasource.common.annotation.DS;
import com.testdatasource.third.configuration.datasource.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* ClassName:DynamicDataSourceAspect
*
* @Description : 自定义切面注解切换数据源
* @version
*/
@Aspect
@Component
@Order(value = 2)
public class DynamicDataSourceAspect { @Pointcut("@annotation(com.testdatasource.common.annotation.DS)")
public void ds() { } @Before("ds()")
public void beforeSwitchDS(JoinPoint point) { // 获得当前访问的class
Class<?> className = point.getTarget().getClass(); // 获得访问的方法名
String methodName = point.getSignature().getName();
// 得到方法的参数的类型
Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass); // 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value().getName();
}
} catch (Exception e) {
e.printStackTrace();
} // 切换数据源
DataSourceContextHolder.setDB(dataSource); } @After("ds()")
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDB();
}
}
yml配置
spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
read_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
read_username: root
read_password: root
write_url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&pinGlobalTxToPhysicalConnection=true
write_username: root
write_password: root
好了 这样配置就没问题了。
Spring 数据库读写分离的更多相关文章
- [转]Spring数据库读写分离
数据库的读写分离简单的说是把对数据库的读和写操作分开对应不同的数据库服务器,这样能有效地减轻数据库压力,也能减轻io压力. 主(master)数据库提供写操作,从(slave)数据库提供读操作,其实在 ...
- Spring aop应用之实现数据库读写分离
Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ...
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- spring+mybatis利用interceptor(plugin)兑现数据库读写分离
使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...
- Spring + Mybatis项目实现数据库读写分离
主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...
- Spring+MyBatis实现数据库读写分离方案
推荐第四种:https://github.com/shawntime/shawn-rwdb 方案1 通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactor ...
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...
- 使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- 在应用层通过spring特性解决数据库读写分离
如何配置mysql数据库的主从? 单机配置mysql主从:http://my.oschina.net/god/blog/496 常见的解决数据库读写分离有两种方案 1.应用层 http://neore ...
随机推荐
- Python 作用域, 局部与全局变量
全局与局部变量 在子程序(函数)中定义的变量称为局部变量, 在程序的一开始定义的变量称为全局变量 全局变量作用于整个程序, 局部变量作用域是定义该变量的子程序 当全局变量与局部变量重名时: 在定义局部 ...
- CopyOnWriteArraySet简介
基于CopyOnWriteArrayList实现,线程安全无需集合. add调用的是CopyOnWriteArraylist的addIfAbsent方法. CopyOnWriteArraySet每次a ...
- Applet web端对文件的读取方式
转载:http://www.exam8.com/computer/Java/zonghe/200708/659876.html ---- 我们知道,在Java Applet中出于安全性考虑,Apple ...
- Java基础-工厂设计模式(三锅的肥鸡)
---恢复内容开始--- 1)还没有工厂时代:假如还没有工业革命,如果一个你要一架飞机,一般的做法是自己去建造一架飞机,然后拿来开 通常的结果就是 有些时候 要么专科螺钉 没打好 要么就是 那个 ...
- github上传文件的几句命令行
1.首先进入要上传的本地目录,右键打开git命令行. 2.执行指令:git init 初始化本地仓库,这是会看到多了一个.git文件夹(如果没看到那就是电脑隐藏了). 3.执行命令:git ad ...
- ApplicationHost.config(IIS存储配置区文件)
对于一个刚刚创建网站,以ASP.NET MVC5为例. 我们并没有在网页的配置文件(web.config)中配置一些处理程序或模块,如处理Session的SessionStateModule模块,映射 ...
- Eureka的基本功能和用法
1.基础架构 eueka按逻辑上可以划分为3个模块,eureka-server,service-provider,service-consumereureka-server:服务端,提供服务注册和发现 ...
- 滴滴 App 的质量优化框架 Booster,开源了!
一. 序 当 App 达到一定体量的时候,肯定是要考虑质量优化.有些小问题,看似只有 0.01% 触发率,但是如果发生在 DAU 过千万的产品中,就很严重了. 滴滴这个独角兽的 DAU 早已过千万,自 ...
- Apache Mina -2
我们可以了解到 mina是个异步通信框架,一般使用场景是服务端开发,长连接.异步通信使用mina是及其方便的.不多说,看例子. 本次mina 使用的例子是使用maven构建的,过程中需要用到的jar包 ...
- MySQL 复制 - 性能与扩展性的基石 1:概述及其原理
1. 复制概述 MySQL 内置的复制功能是构建基于 MySQL 的大规模.高性能应用的基础,复制解决的基本问题是让一台服务器的数据与其他服务器保持同步. 接下来,我们将从复制概述及原理.复制的配置. ...