springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14
原文:https://blog.csdn.net/wsbgmofo/article/details/79260896
1,数据源配置文件,如下
datasource.readSize=1
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 主数据源,默认的
spring.master.driver-class-name=com.mysql.jdbc.Driver
spring.master.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.master.username=root
spring.master.password=root
spring.master.initialSize=5
spring.master.minIdle=5
spring.master.maxActive=50
spring.master.maxWait=60000
spring.master.timeBetweenEvictionRunsMillis=60000
spring.master.minEvictableIdleTimeMillis=300000
spring.master.poolPreparedStatements=true
spring.master.maxPoolPreparedStatementPerConnectionSize=20
# 从数据源
spring.slave.driver-class-name=com.mysql.jdbc.Driver
spring.slave.url=jdbc:mysql://localhost:3306/data_source_02?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
spring.slave.username=root
spring.slave.password=root
spring.slave.initialSize=5
spring.slave.minIdle=5
spring.slave.maxActive=50
spring.slave.maxWait=60000
spring.slave.timeBetweenEvictionRunsMillis=60000
spring.slave.minEvictableIdleTimeMillis=300000
spring.slave.poolPreparedStatements=true
spring.slave.maxPoolPreparedStatementPerConnectionSize=20
2,新建数据库配置类DataSourceConfiguration,如下
package com.aop.writeAndRead.config;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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;
@Configuration
public class DataSourceConfiguration {
private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name="writeDataSource", destroyMethod = "close", initMethod="init")
@Primary
@ConfigurationProperties(prefix = "spring.master")
public DataSource writeDataSource() {
log.info("-------------------- writeDataSource init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 有多少个从库就要配置多少个
* @return
*/
@Bean(name = "readDataSource", destroyMethod = "close", initMethod="init")
@ConfigurationProperties(prefix = "spring.slave")
public DataSource readDataSourceOne(){
log.info("-------------------- readDataSourceOne init ---------------------");
return DataSourceBuilder.create().type(dataSourceType).build();
}
/**
* 这里的list是多个从库的情况下为了实现简单负载均衡
* @return
* @throws SQLException
*/
@Bean("readDataSources")
public List<DataSource> readDataSources() throws SQLException{
List<DataSource> dataSources=new ArrayList<>();
dataSources.add(readDataSourceOne());
return dataSources;
}
}
3,新建DataSourceContextHolder类,根据ThreadLocal来实现数据源的动态改变,如下
package com.aop.writeAndRead.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DataSourceContextHolder {
private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
private static final ThreadLocal<String> local = new ThreadLocal<String>();
public static ThreadLocal<String> getLocal() {
return local;
}
/**
* 读可能是多个库
*/
public static void read() {
local.set(DataSourceType.read.getType());
System.out.println("==:" + DataSourceType.read.getType());
log.info("数据库切换到读库...");
}
/**
* 写只有一个库
*/
public static void write() {
local.set(DataSourceType.write.getType());
log.info("数据库切换到写库...");
}
public static String getJdbcType() {
return local.get();
}
}
4,新建一个枚举类DataSourceType,如下
package com.aop.writeAndRead.config;
public enum DataSourceType {
read("read", "从库"),
write("write", "主库");
private String type;
private String name;
DataSourceType(String type, String name) {
this.type = type;
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
5,新建MybatisConfiguration类,如下
package com.aop.writeAndRead.config;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataSourceConfiguration.class})
@MapperScan(basePackages={"com.aop.writeAndRead.mapper"})
public class MybatisConfiguration {
@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Value("${datasource.readSize}")
private String dataSourceSize;
// @Resource(name = "writeDataSource")
// private DataSource writeDataSource;
// @Qualifier("readDataSource")
// private DataSource readDataSource;
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(ApplicationContext ac) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy(ac));
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:mappings/**/*.xml"));
sqlSessionFactoryBean.setTypeAliasesPackage("com.aop.writeAndRead.entity");
sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sqlSessionFactoryBean.getObject();
}
/**
* 有多少个数据源就要配置多少个bean
* @return
*/
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy(ApplicationContext ac) {
int size = Integer.parseInt(dataSourceSize);
System.out.println("size:" + size);
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
//多个读数据库时
DataSource writeDataSource = (DataSource)ac.getBean("writeDataSource");
List<DataSource> readDataSources = (List<DataSource>)ac.getBean("readDataSources");
for (int i = 0; i < size; i++) {
targetDataSources.put(i, readDataSources.get(i));
}
proxy.setDefaultTargetDataSource(writeDataSource);
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
}
把第2步注册的bean放入一个map里面,后面就可以动态从这个map里面获取对应的数据源
注意:之前报错的地方就是在这里,用@Resource和@Qualifier这两种方式都无法获取到第2步注册的bean,只能是通过applicationContext上下文获取,应该是跟注解的优先级有关,Resource和Qualifier先执行,这个时候第2步的bean还未注册,所以娶不到,如果有知道更详细原因的朋友,请留言告知
6,,新建MyAbstractRoutingDataSource,如下
package com.aop.writeAndRead.config;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
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);
}
}
这里的determineCurrentLookupKey方法是根据DataSourceContextHolder这个类所改变的数据源而返回对应的bean的key,
这里的key要跟第5步放入map里面的key对应上
7,新建springAOP类,如下
package com.aop.writeAndRead.config;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAop {
private static Logger log = LoggerFactory.getLogger(DataSourceAop.class);
@Pointcut("@annotation(com.aop.writeAndRead.config.WriteDataSource)")
public void writeMethod(){}
@Pointcut("@annotation(com.aop.writeAndRead.config.ReadDataSource)")
public void readMethod(){}
@Before("writeMethod()")
public void beforeWrite(JoinPoint point) {
DataSourceContextHolder.write();
String className = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
System.out.println("开始执行:"+className+"."+methodName+"()方法...");
log.info("dataSource切换到:write");
}
@Before("readMethod()")
public void beforeRead(JoinPoint point) throws ClassNotFoundException {
//设置数据库为读数据
DataSourceContextHolder.read();
/*spring AOP测试代码*/
String currentClassName = point.getTarget().getClass().getName();//根据切点获取当前调用的类名
String methodName = point.getSignature().getName();//根据切点获取当前调用的类方法
Object[] args = point.getArgs();//根据切点获取当前类方法的参数
System.out.println("开始执行:"+currentClassName+"."+methodName+"()方法...");
Class reflexClassName = Class.forName(currentClassName);//根据反射获取当前调用类的实例
Method[] methods = reflexClassName.getMethods();//获取该实例的所有方法
for(Method method : methods){
if(method.getName().equals(methodName)){
String desrciption = method.getAnnotation(ReadDataSource.class).description();//获取该实例方法上注解里面的描述信息
System.out.println("desrciption:" + desrciption);
}
}
log.info("dataSource切换到:Read");
}
}
利用springAOP对方法的切入,在方法执行前判断使用哪个数据源
@Pointcut("@annotation(com.aop.writeAndRead.config.WriteDataSource)")
这里是对自定义注解作切点,双引号里面也可以换成对方法,但是个人觉得如果对方法作切点的话,如果方法多了这里写的就很长了
8,新建注解类,如下
package com.aop.writeAndRead.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadDataSource {
String description() default "";
}
package com.aop.writeAndRead.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteDataSource {
String description() default "";
}
这样在对需要控制数据源的方法前加上这个注解,springAOP就能控制这个方法,先选择数据源再执行方法
测试:
在方法上加入注解,如下
package com.aop.writeAndRead.service;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.aop.writeAndRead.config.ReadDataSource;
import com.aop.writeAndRead.config.WriteDataSource;
import com.aop.writeAndRead.entity.User;
import com.aop.writeAndRead.mapper.UserMapper;
@Service
public class UserService {
@Autowired UserMapper userMapper;
@WriteDataSource(description="WRITE")
public void writeUser(User user){
userMapper.writeUser(user);
}
@ReadDataSource(description="READ")
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=true)
public Map<String, String> readUser(){
return userMapper.readUser();
}
}
接口分别调用writeUser跟readUser,如下
9,MySQL数据同步
修改主库的配置文件my.ini,在末尾加上
#数据库ID号, 为1时表示为Master,其中master_id必须为1到232–1之间的一个正整数值;
server-id = 1
#启用二进制日志;
log-bin=mysql-bin
#需要同步的二进制数据库名;
binlog-do-db=minishop
#不同步的二进制数据库名,如果不设置可以将其注释掉;
binlog-ignore-db=information_schema
binlog-ignore-db=mysql
binlog-ignore-db=personalsite
binlog-ignore-db=test
#设定生成的log文件名;
log-bin="D:/Database/materlog"
#把更新的记录写到二进制文件中;
log-slave-updates
修改从库的配置文件my.ini,在文件末尾加上
#如果需要增加Slave库则,此id往后顺延;
server-id = 2
log-bin=mysql-bin
#主库host
master-host = 192.168.168.253
#在主数据库服务器中建立的用于该从服务器备份使用的用户
master-user = forslave
master-password = ******
master-port = 3306
#如果发现主服务器断线,重新连接的时间差;
master-connect-retry=60
#不需要备份的数据库;
replicate-ignore-db=mysql
#需要备份的数据库
replicate-do-db=minishop
log-slave-update
---------------------
作者:猴样鬼相
来源:CSDN
原文:https://blog.csdn.net/wsbgmofo/article/details/79260896
版权声明:本文为博主原创文章,转载请附上博文链接!
springboot+springAOP实现数据库读写分离及数据库同步(MySQL)----最新可用2019-2-14的更多相关文章
- centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数据库读写分离 双主搭建 mysql.history 第二十九节课
centos MySQL主从配置 ntsysv chkconfig setup命令 配置MySQL 主从 子shell MySQL备份 kill命令 pid文件 discuz!论坛数 ...
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...
- 使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- 数据库读写分离Master-Slave
数据库读写分离Master-Slave 一个平台或系统随着时间的推移和用户量的增多,数据库操作往往会变慢,这时我们需要一些有效的优化手段来提高数据库的执行速度:如SQL优化.表结构优化.索引优化.引擎 ...
- Spring aop应用之实现数据库读写分离
Spring加Mybatis实现MySQL数据库主从读写分离 ,实现的原理是配置了多套数据源,相应的sqlsessionfactory,transactionmanager和事务代理各配置了一套,如果 ...
- CYQ.Data V5 数据库读写分离功能介绍
前言 好多年没写关于此框架的新功能的介绍了,这些年一直在默默地更新,从Nuget上的记录就可以看出来: 这几天在看Java的一些东西,除了觉的Java和.NET的相似度实在太高之外,就是Java太原始 ...
- ThinkPHP v3.2.3 数据库读写分离,开启事务时报错:There is no active transaction
如题:ThinkPHP v3.2.3 数据库读写分离,开启事务时报错: ERR: There is no active transaction 刚开始以为是数据表引擎不对造成的,因为 有几张表的引擎是 ...
- EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~续~添加事务机制
回到目录 上一讲中简单介绍了一个EF环境下通过DbCommand拦截器来实现SQLSERVER的读写分离,只是一个最简单的实现,而如果出现事务情况,还是会有一些问题的,因为在拦截器中我们手动开启了Co ...
随机推荐
- ejs循环实例
... //index page var items=[{title:"文章1"},{title:"文章2"}]; app.get('/',function(r ...
- JSON相关
- Delphi控件开发浅入深出(三)
三.开关控件TlincoSwitch 用过Delphi1(好古老的东东呀!)的人相信都记得这个开关控件 ,不知道当初Borland为什么把这么一个在开发普通应用程序中应用不到的工控控件放到Delphi ...
- JVM调优思路
一.jvm内存调优 (Gc 和 Full gc) hotspot -Xms40m 最小堆内存 -Xmx512m 最大值内存 -verboose:gc -XX:PrintGCDetails -XX: ...
- 在移动网页网页上点击链接跳转到QQ聊天界面
打开qq聊天窗口的方法 <a href="http://wpa.qq.com/msgrd?v=3&uin=1450612626&site=qq&menu=yes ...
- java中class.forName和classLoader加载类的区分
java中class.forName和classLoader都可用来对类进行加载.前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块.而classLoade ...
- JMeter 七:远程测试
参考:http://jmeter.apache.org/usermanual/remote-test.html http://jmeter.apache.org/usermanual/jmeter_d ...
- Php如何返回json数据
php返回json,xml,JSONP等格式的数据 返回json数据: header('Content-Type:application/json; charset=utf-8'); $arr = a ...
- Mysql 如何查询两个时间段之间的数据?
Mysql 如何查询两个时间段之间的数据?
- Android so文件生成
http://blog.csdn.net/laczff21/article/details/7542236 http://blog.csdn.net/yhm2046/article/details/8 ...