Spring + Mybatis环境实现Mysql数据库主从切换
来源:互联网 发布:强直性脊柱炎 知乎 编辑:程序博客网 时间:2024/06/14 17:37
一、简述:
数据库应用场景中,经常是“读多写少”,也就是对数据库读取数据压力比较大。有一个解决方案是采用数据库集群方案。
一个数据库是主库,负责写;其他为从库,负责读。实现:读写分离。
那么,对我们的要求是:
1. 读库和写库的数据一致;
2. 写数据必须写到写库;
3. 读数据必须到读库;
二、方案:
实现读写分离有两种方案:应用层解决和中间件解决;
本篇,介绍使用Spring方式,实现应用层解决方式。
三、原理:
在进入Service之前,使用AOP来做出判断,是使用写库还是读库,判断依据可以根据方法名判断,比如说以query、find、get等开头的就走读库,其他的走写库
说明:也可使用mybatis插件方式,通过判断语句INSERT,UPDATE,DELETE,SELECT判断;(推荐)
四、实际解决方案:
- 方案一:Spring AOP注解方式;
- 方案二:Spring AOP接口方法名限定方式;
- 方案三:Mybatis 插件判断SQL语句方式;(推荐)
以下使用方案三:
参与角色:
1、RouteDataSourceKeyEnum 主从数据库枚举
2、RouteDataSource 动态切换数据源
3、RouteDataSourcePlugin MyBtais拦截插件
4、spring-mybatis.xml 数据源,切换类配置文件
代码:
1、RouteDataSourceKeyEnum.java文件:
/** * @author devin * @date 2017/11/1 */public enum RouteDataSourceKeyEnum { MASTER, SLAVE}
2、RouteDataSource.java文件:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * @author devin * @date 2017/10/23 */public class RouteDataSource extends AbstractRoutingDataSource { /** * dbKey线程安全容器 */ private static ThreadLocal<String> holder = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return holder.get(); } /** * 设置dbKey * * @param dbKey */ public static void setDbKey(RouteDataSourceKeyEnum dbKey) { holder.set(dbKey.name()); }}
3、RouteDataSourcePlugin.java文件:
import com.framework.datasource.DynamicDataSourceGlobal;import com.framework.datasource.DynamicDataSourceHolder;import com.framework.util.GlobalKeys;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;/** * @author devin * @date 2017/10/23 * 在DefaultSqlSession的insert,delete方法也是调用了update方法 */@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 RouteDataSourcePlugin implements Interceptor { protected static final Logger logger = LoggerFactory.getLogger(RouteDataSourcePlugin.class); private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*"; private static final Map<String, RouteDataSourceKeyEnum> cacheMap = new ConcurrentHashMap<>(); @Override public Object intercept(Invocation invocation) throws Throwable { //常量切换 生产 测试库 boolean gamma = Boolean.parseBoolean(GlobalKeys.getString("db.source.switch")); if (gamma) { RouteDataSource.setDbKey(RouteDataSourceKeyEnum.MASTER); return invocation.proceed(); } else if (!gamma) { RouteDataSource.setDbKey(RouteDataSourceKeyEnum.SLAVE); return invocation.proceed(); } //它首先查看当前是否存在事务管理上下文,并尝试从事务管理上下文获取连接,如果获取失败,直接从数据源中获取连接。 //在获取连接后,如果当前拥有事务上下文,则将连接绑定到事务上下文中。(此处直接继续下一过程) boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive(); if (!synchronizationActive) { Object[] objects = invocation.getArgs(); MappedStatement ms = (MappedStatement) objects[0]; RouteDataSourceKeyEnum routeDataSourceKeyEnum = null; if ((routeDataSourceKeyEnum = 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)) { routeDataSourceKeyEnum = RouteDataSourceKeyEnum.MASTER; } else { BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]); String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " "); if (sql.matches(REGEX)) { routeDataSourceKeyEnum = RouteDataSourceKeyEnum.MASTER; } else { routeDataSourceKeyEnum = RouteDataSourceKeyEnum.SLAVE; } } } else { routeDataSourceKeyEnum = RouteDataSourceKeyEnum.MASTER; } logger.warn("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(), routeDataSourceKeyEnum.name(), ms.getSqlCommandType().name()); cacheMap.put(ms.getId(), routeDataSourceKeyEnum); } RouteDataSource.setDbKey(routeDataSourceKeyEnum); } 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) { // }}
4、spring-mybatis.xml文件:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd"> <!-- 引入配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties"/> </bean> <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> <!-- 初始化连接大小 --> <property name="initialSize" value="${initialSize}"></property> <!-- 连接池最大数量 --> <property name="maxActive" value="${maxActive}"></property> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${maxIdle}"></property> <!-- 连接池最小空闲 --> <property name="minIdle" value="${minIdle}"></property> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${maxWait}"></property> </bean> <bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url_slave}"/> <property name="username" value="${username_slave}"/> <property name="password" value="${password_slave}"/> <!-- 初始化连接大小 --> <property name="initialSize" value="${initialSize}"></property> <!-- 连接池最大数量 --> <property name="maxActive" value="${maxActive}"></property> <!-- 连接池最大空闲 --> <property name="maxIdle" value="${maxIdle}"></property> <!-- 连接池最小空闲 --> <property name="minIdle" value="${minIdle}"></property> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="${maxWait}"></property> </bean> <!--动态获取数据库--> <bean id="dataSource" class="com.framework.routedb.RouteDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!--写库--> <entry key="MASTER" value-ref="masterDataSource"/> <!--读库--> <entry key="SLAVE" value-ref="slaveDataSource"/> </map> </property> <property name="defaultTargetDataSource" ref="masterDataSource"></property> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自动扫描mapping.xml文件 --> <property name="mapperLocations" value="classpath:mapper/**/*.xml"></property> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="plugins"> <list> <bean class="com.huxin.assets.framework.routedb.RouteDataSourcePlugin"></bean> </list> </property> </bean> <!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.**.dao"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean> <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean></beans>
参考资料:
- 使用Spring实现读写分离( MySQL实现主从复制)
- Spring+MyBatis实现数据库读写分离方案
- spring+spring mvc +mybatis+druid 实现数据库主从分离
阅读全文
0 0
- Spring + Mybatis环境实现Mysql数据库主从切换
- spring+hibernate+mysql实现主从数据库动态切换
- mysql主从数据库切换
- springMvc-Mybatis 实现主从数据库/多数据源切换配置
- spring+spring mvc +mybatis+druid 实现数据库主从分离
- spring-spring mvc-mybatis 实现主从数据库配置
- spring+spring mvc +mybatis+druid 实现数据库主从分离
- spring-spring mvc-mybatis 实现主从数据库配置
- spring-spring mvc-mybatis 实现主从数据库配置
- spring+spring mvc +mybatis+druid 实现数据库主从分离
- spring+spring mvc +mybatis+druid 实现数据库主从分离
- Spring+Mybatis配置主从数据库
- mysql+spring+mybatis实现数据库读写分离
- 实现spring+mybatis+uncode dal,应用自动切换连接数据库
- 京东MySQL数据库主从切换自动化
- 京东MySQL数据库主从切换自动化
- 京东MySQL数据库主从切换自动化
- keepalived 实现mysql主从自动切换
- Toppay 数据格式转换
- 【Mybatis学习】框架中使用到的设计模式
- NumberUtils简单介绍和具体实例
- C#中BadImageFormatException异常
- access数据库和SQL数据库bit类型的差别
- Spring + Mybatis环境实现Mysql数据库主从切换
- Http请求中Content-Type讲解
- 支持嵌套滚动的控件NestedScrollView
- 矢量图形引擎库VectorDraw Developer Framework更新至v7.7012.1.1,周年8.5折限时特惠!
- log4j.properties配置详解与实例
- 多线程基础学习七:使用synchronized实现多线程情况下的访问次数统计
- This application failed to start because it could not find or load the Qt platform ...
- 穿透内网,连接动态ip,内网ip打洞-----p2p实现原理
- PAT考试乙级1014(C语言实现) 部分正确