Springboot Mybatis MySQL读写分离及事物配置
为什么需要读写分离
当项目越来越大和并发越来大的情况下,单个数据库服务器的压力肯定也是越来越大,最终演变成数据库成为性能的瓶颈,而且当数据越来越多时,查询也更加耗费时间,当然数据库数据过大时,可以采用数据库分库分表,同时数据库压力过大时,也可以采用Redis等缓存技术来降低压力,但是任何一种技术都不是万金油,很多时候都是通过多种技术搭配使用,而本文主要就是介绍通过读写分离来加快数据库读取速度
实现方式
读写分离实现的方式有多种,但是多种都需要配置数据库的主从复制,当然也许是有不需要配置的,只是我不知道而已
方式一
数据库中间件实现,如Mycat等数据库中间件,对于项目本身来说,只有一个数据源,就是链接到Mycat,再由mycat根据规则去选择从哪个库获取数据
方式二
代码中配置多数据源,通过代码控制使用哪个数据源,本文也是主要介绍这种方式
读写分离优劣
优点
1.降低数据库读取压力,尤其是有些需要大量计算的实时报表类应用
2.增强数据安全性,读写分离有个好处就是数据近乎实时备份,一旦某台服务器硬盘发生了损坏,从库的数据可以无限接近主库
3.可以实现高可用,当然只是配置了读写分离并不能实现搞可用,最多就是在Master(主库)宕机了还能进行查询操作,具体高可用还需要其他操作
缺点
1.增大成本,一台数据库服务器和多台数据库的成本肯定是不一样的
2.增大代码复杂度,不过这点还比较轻微吧,但是也的确会一定程度上加重
3.增大写入成本,虽然降低了读取成本,但是写入成本却是一点也没有降低,毕竟还有从库一直在向主库请求数据
MySQL主从复制配置
MySQL主从配置是实现读写分离的基本条件,具体实现MySQL主从复制可以参考我之前的文章MySQL主从复制搭建,基于日志(binlog)
数据源配置
spring:application:name: separatemaster:url: jdbc:mysql://192.168.1.126:3307/test?useUnicode=true&characterEncoding=utf8&emptyStringsConvertToZero=trueusername: rootpassword: 123456driver_class_namel: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemax-active: 20initial-size: 1min-idle: 3max-wait: 600time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000test-while-idle: truetest-on-borrow: falsetest-on-return: falsepoolPreparedStatements: trueslave:url: jdbc:mysql://192.168.1.126:3309/test?useUnicode=true&characterEncoding=utf8&emptyStringsConvertToZero=trueusername: testpassword: 123456driver_class_namel: com.mysql.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourcemax-active: 20initial-size: 1min-idle: 3max-wait: 600time-between-eviction-runs-millis: 60000min-evictable-idle-time-millis: 300000test-while-idle: truetest-on-borrow: falsetest-on-return: falsepoolPreparedStatements: true
文件中配置了2个数据源,master是写库,slave是读库,为了防止向slave写入,slave的用户只有读取权限
因为代码中需要动态的设置数据源,所以数据源需要通过继承AbstractRoutingDataSource
/*** 动态数据源* @author Raye* @since 2016年10月25日15:20:40*/
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();@Overrideprotected Object determineCurrentLookupKey() {return contextHolder.get();}public static enum DatabaseType {Master, Slave}public static void master(){contextHolder.set(DatabaseType.Master);}public static void slave(){contextHolder.set(DatabaseType.Slave);}public static void setDatabaseType(DatabaseType type) {contextHolder.set(type);}public static DatabaseType getType(){return contextHolder.get();}
}
contextHolder 是线程变量,因为每个请求是一个线程,所以通过这样来区分使用哪个库
determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法,主要是确定当前应该使用哪个数据源的key,因为AbstractRoutingDataSource 中保存的多个数据源是通过Map的方式保存的
实例化数据源
/*** Druid的DataResource配置类* @author Raye* @since 2016年10月7日14:14:18*/
@Configuration
@EnableTransactionManagement
public class DataBaseConfiguration implements EnvironmentAware {private RelaxedPropertyResolver propertyResolver1;private RelaxedPropertyResolver propertyResolver2;public DataBaseConfiguration(){System.out.println("#################### DataBaseConfiguration");}public void setEnvironment(Environment env) {this.propertyResolver1 = new RelaxedPropertyResolver(env, "spring.master.");this.propertyResolver2 = new RelaxedPropertyResolver(env, "spring.slave.");}public DataSource master() {System.out.println("注入Master druid!!!");DruidDataSource datasource = new DruidDataSource();datasource.setUrl(propertyResolver1.getProperty("url"));datasource.setDriverClassName(propertyResolver1.getProperty("driver-class-name"));datasource.setUsername(propertyResolver1.getProperty("username"));datasource.setPassword(propertyResolver1.getProperty("password"));datasource.setInitialSize(Integer.valueOf(propertyResolver1.getProperty("initial-size")));datasource.setMinIdle(Integer.valueOf(propertyResolver1.getProperty("min-idle")));datasource.setMaxWait(Long.valueOf(propertyResolver1.getProperty("max-wait")));datasource.setMaxActive(Integer.valueOf(propertyResolver1.getProperty("max-active")));datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver1.getProperty("min-evictable-idle-time-millis")));try {datasource.setFilters("stat,wall");} catch (SQLException e) {e.printStackTrace();}return datasource;}public DataSource slave() {System.out.println("Slave druid!!!");DruidDataSource datasource = new DruidDataSource();datasource.setUrl(propertyResolver2.getProperty("url"));datasource.setDriverClassName(propertyResolver2.getProperty("driver-class-name"));datasource.setUsername(propertyResolver2.getProperty("username"));datasource.setPassword(propertyResolver2.getProperty("password"));datasource.setInitialSize(Integer.valueOf(propertyResolver2.getProperty("initial-size")));datasource.setMinIdle(Integer.valueOf(propertyResolver2.getProperty("min-idle")));datasource.setMaxWait(Long.valueOf(propertyResolver2.getProperty("max-wait")));datasource.setMaxActive(Integer.valueOf(propertyResolver2.getProperty("max-active")));datasource.setMinEvictableIdleTimeMillis(Long.valueOf(propertyResolver2.getProperty("min-evictable-idle-time-millis")));try {datasource.setFilters("stat,wall");} catch (SQLException e) {e.printStackTrace();}return datasource;}@Beanpublic DynamicDataSource dynamicDataSource() {DataSource master = master();DataSource slave = slave();Map<Object, Object> targetDataSources = new HashMap<Object, Object>();targetDataSources.put(DynamicDataSource.DatabaseType.Master, master);targetDataSources.put(DynamicDataSource.DatabaseType.Slave, slave);DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法dataSource.setDefaultTargetDataSource(master);return dataSource;}}
一共有3个数据源,一个master,一个slave,一个是动态数据源,保存在master和slave,为了防止spring注入异常,所以master和slave都是主动实例化的,并不是交给spring管理
dataSource.setDefaultTargetDataSource(master);
是配置的如果没有配置当前使用哪个数据源的默认数据源,本来是打算配置slave,但是因为事物问题,所以配置的master
Mybatis配置
/*** MyBatis的配置类* * @author Raye* @since 2016年10月7日14:13:39*/
@Configuration
@AutoConfigureAfter({ DataBaseConfiguration.class })
@Slf4j
public class MybatisConfiguration {@Bean(name = "sqlSessionFactory")@Autowiredpublic SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dynamicDataSource);try {SqlSessionFactory session = bean.getObject();MapperHelper mapperHelper = new MapperHelper();//特殊配置Config config = new Config();//具体支持的参数看后面的文档config.setNotEmpty(true);//设置配置mapperHelper.setConfig(config);// 注册自己项目中使用的通用Mapper接口,这里没有默认值,必须手动注册mapperHelper.registerMapper(Mapper.class);//配置完成后,执行下面的操作mapperHelper.processConfiguration(session.getConfiguration());return session;} catch (Exception e) {e.printStackTrace();}return null;}@Bean(name = "sqlSessionTemplate")@Autowiredpublic SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}@Beanpublic MapperScannerConfigurer scannerConfigurer(){MapperScannerConfigurer configurer = new MapperScannerConfigurer();configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");configurer.setSqlSessionTemplateBeanName("sqlSessionTemplate");configurer.setBasePackage("wang.raye.**.mapper");configurer.setMarkerInterface(Mapper.class);return configurer;}
}
MybatisConfiguration 主要是配置的sqlSessionFactory和sqlSessionTemplate,以及Mybatis的扩展框架Mapper的配置,如果不需要Mapper,可以不用配置scannerConfigurer
事物配置
@Configuration
@EnableTransactionManagement
@Slf4j
@AutoConfigureAfter({ MybatisConfiguration.class })
public class TransactionConfiguration extends DataSourceTransactionManagerAutoConfiguration {@Bean@Autowiredpublic DataSourceTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {log.info("事物配置");return new DataSourceTransactionManager(dynamicDataSource);}
}
事物配置这里有一个坑就是,一旦开启了事物,好像就会切换线程执行,所以并不会使用当前配置的数据源,而会取到默认的数据源,所以只能通过将默认数据源设置为master
AOP切入设置数据源
/*** 数据源的切入面**/
@Aspect
@Component
@Slf4j
public class DataSourceAOP {@Before("execution(* wang.raye.separate.service..*.select*(..)) || execution(* wang.raye.separate.service..*.get*(..))")public void setReadDataSourceType() {DynamicDataSource.slave();log.info("dataSource切换到:slave");}@Before("execution(* wang.raye.separate.service..*.insert*(..)) || execution(* wang.raye.separate.service..*.update*(..)) || execution(* wang.raye.separate.service..*.delete*(..)) || execution(* wang.raye.separate.service..*.add*(..))")public void setWriteDataSourceType() {DynamicDataSource.master();log.info("dataSource切换到:master");}}
这样的配置是根据方法名来的,可以根据自己的情况配置
也可以使用注解来主动切换,创建两个注解类,一个Master,一个Slave
Master.class
/*** 使用主库的注解*/
public @interface Master {}
Slave.class
/*** 使用读库的注解*/
public @interface Slave {}
AOP切入修改
/*** 数据源的切入面**/
@Aspect
@Component
@Slf4j
public class DataSourceAOP {@Before("(@annotation(wang.raye.separate.annotation.Master) || execution(* wang.raye.separate.service..*.insert*(..)) || " +"execution(* wang.raye.separate.service..*.update*(..)) || execution(* wang.raye.separate.service..*.delete*(..)) || " +"execution(* wang.raye.separate.service..*.add*(..))) && !@annotation(wang.raye.separate.annotation.Slave) -")public void setWriteDataSourceType() {DynamicDataSource.master();log.info("dataSource切换到:master");}@Before("(@annotation(wang.raye.separate.annotation.Slave) || execution(* wang.raye.separate.service..*.select*(..)) || execution(* wang.raye.separate.service..*.get*(..))) && !@annotation(wang.raye.separate.annotation.Master)")public void setReadDataSourceType() {DynamicDataSource.slave();log.info("dataSource切换到:slave");}}
注:这个AOP切入规则只是包含基本的规格,如果要正常使用,需要扩展规则
简单的service层代码
/*** 用户相关业务接口实现类*/
@Service
@Slf4j
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper mapper;@Master@Overridepublic List<User> selectAll() {return mapper.selectAll();}@Overridepublic boolean addUser(User user) {return mapper.insertSelective(user) > 0;}@Overridepublic boolean updateUser(User user) {return mapper.updateByPrimaryKey(user) > 0;}@Overridepublic boolean deleteByid(int id) {return mapper.deleteByPrimaryKey(id) > 0;}@Transactional(rollbackFor = Exception.class )@Overridepublic boolean insertAndUpdate(User user){log.info("当前key:"+ DynamicDataSource.getType().name());int count = 0;count += mapper.insertSelective(user);user = null;user.getId();count += mapper.updateByPrimaryKey(user);return count > 1;}
}
这里所有方法会使用master源,如果去掉selectAll的Master注解,那么selectAll就会使用slave数据源,insertAndUpdate方法主要是测试使用事物的情况下是否是向Master数据源写入以及是否正常回滚
源码
具体代码可以直接看我的demo项目读写分离demo
Springboot Mybatis MySQL读写分离及事物配置相关推荐
- SpringBoot + MyBatis + MySQL 读写分离实战
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:http://t.cn/AiKuJEB9 1. 引言 读写分 ...
- MySQL的主从配置+SpringBoot的MySQL读写分离配置
MySQL的主从复制 点击前往查看MySQL的安装 1.主库操作 vim /etc/my.cnf 添加如下配置 log-bin=mysql-bin #[必须]启用二进制日志 server-id=128 ...
- mysql读写分离,主从配置
2019独角兽企业重金招聘Python工程师标准>>> 一个完整的mysql读写分离环境包括以下几个部分: 应用程序client database proxy database集群 ...
- mysql读写分离的完整配置
参考文章: 文章一[仅供参考]: 构建高性能web之路------mysql读写分离实战[按照里面配置主从mysql同步失败,并且按照他的my.cnf配置,给我的虚拟机搞坏了,重新弄了一个] http ...
- springboot+mybatis+mybatis +mysql读写分离(AOP方式)
引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式. 第一种是依靠中间 ...
- Spring Boot + MyBatis + MySQL读写分离
今日推荐 借助Redis锁,完美解决高并发秒杀问题还在直接用JWT做鉴权?JJWT真香Spring Boot 操作 Redis 的各种实现Fluent Mybatis 牛逼!Nginx 常用配置清单这 ...
- boot lib分离 spring_SpringBoot+MyBatis+MySQL读写分离(实例)A
1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿,无非两个,要么中间件帮我们做,要么程序自己做.因此,一般来讲,读写分离有两种实现方式.第一种是依靠 ...
- mysql读写分离实现_laravel 配置MySQL读写分离
前言:说到应对大流量.高并发的解决方案的时候,总会有这样的回答,如:读写分离,主从复制...等,数据库层今天先不讨论,那么今天我们就来看看怎么在应用层实现读写分离. 框架:laravel5.7 说明: ...
- mysql读写分离实例_SpringBoot+MyBatis+MySQL读写分离(实例)
https://mp.weixin.qq.com/s/1vTbllmZkHRnuk7_xR2DIg 1. 引言 读写分离要做的事情就是对于一条SQL该选择哪个数据库去执行,至于谁来做选择数据库这件事儿 ...
最新文章
- mysql druid 多数据源_SpringBoot使用阿里数据库连接池Druid以及多数据源配置
- 成功解决tensorflow\contrib\learn\python\learn\datasets\base._internal_retry.locals.wrap.locals.wrapp
- 成功解决pywintypes.error: (2, 'LoadLibraryEx', '系统找不到指定的文件。')
- [WorldWind学习]5.相机对象
- Linux debian安装Notepadqq,Linux系统下的Notepad++编辑器
- Nginx handler模块
- Spring @Autowired Annotation
- 计蒜客---函数规律
- Javascript函数调用的四种方法
- CCF认证-2014-12-2 Z字形扫描
- memcache连接是否有用户名和密码的设置
- Python: 生成器,yield
- oracle10g在win10上的安装
- 714 买卖股票的最佳时机含手续费(状态机dp)
- 一文带你认识HTML
- HNU 11722 The Gougu Theorem
- 讲座 | lidar目标检测------图森未来CTO王乃岩
- 徐家骏:华为十年感悟(转载)
- Effective C++条款39:明智而审慎地使用private继承(Use private inheritance judiciously)
- Ray----Tune(2):Tune的用户指南
热门文章
- 做报表到10点才下班,做的还是丑,怎样才能做出一张好看的报表?
- 解决虚拟机-虚拟网络配置没有桥接模式,本地没有虚拟网卡
- 全球及中国芯片产业研发方向与投资规模预测报告2022版
- openlayers3 ol3热力图 json
- 配置Hadoop格式化namenode时报错cannot create directory /usr/local/hadoop/tmp/dfs/name/current
- python 高级部分
- 止血、回血 苏宁易购正在复苏路上
- java md5 源码_MD5加密 Java源代码
- 休问情怀谁得似——冰雪小五台苦旅记(十完结篇)
- 人工智能阿发狗技术都包含哪些内容