Spring MVC+jdbcTemplate+MySql实现读写分离
2016-12-29 10:01
363 查看
现如今,数据库读写分离已经不在是新鲜的话题,很多数据统计网站 、访问量高的网站都会使用这项技术。解决网站响应慢、服务器承载压力过大的方案其实有很多,而读写分离只是其中一种实现方案。有的时候读写分离并不能完美解决。此文章记录了笔者使用@Aspect注解方式实现Spring MVC+jdbcTemplate+MySql实现读写分离的过程。
既然要读写分离,那么数据库至少就要又两个,一个主数据库,负责读写,多个重数据库,负责读取。
既然是读写分离,为什么主数据库还要负责读和写呢?
在实际的应用场景种,
一、我写入数据的同时往往要读取一些基础数据。
二、数据在写入之后要读取出相关信息,读写分离再及时,也会产生延迟,这样程序上就会异常了。
所以,主数据库不仅需要写入的能力,还要有读取的能力。
笔者实用MySql数据库,jdbc.properties配置如下
其中,mysqlM是主数据库,mysqlC是重数据库。
spring-dao.xml配置如下
注解:
1、
spring默认是不开启Aspect(自定义标签注入)模式的,需要手动开启。
2、
自定义类:DataSourceAspect,目的是创建自定义标签、绑定标签实现方法。代码如下:
DataSourceChange 类
3、
自定义类DynamicDataSource,动态数据源、主从库选择。这一切都要靠spring的AbstractRoutingDataSource,所以我们要继承AbstractRoutingDataSource。其实可以想一下,如果我们自己要实现读写分离,我们自己的思路是什么。
问题一、什么时候使用主库,什么时候使用重库。
问题二、什么时候去切换数据库。
第一个问题其实很好解释,主重的选择完全靠人为来决定,我们给方法、类或者接口加上自定义标签后,我们的程序就能判断出你期望使用的数据源。
第二个问题、很多人都会想到是在dao层查询的时候去选择dataSource,没错,spring也是这么想的,所以它提供了我们一个类:AbstractRoutingDataSource。
方法1:afterPropertiesSet,数据源初始化
方法2:determineCurrentLookupKey,翻译过来的意思是:根据key来选择数据源,就是说我们要访问数据库前,AbstractRoutingDataSource中的determineCurrentLookupKey方法,帮我们去选择dataSource,我们需要告诉spring我们要使用哪个数据源,也就是Key。
以上就是读写分离的基本配置。
在程序中断点一下,可以看到每个类的先后执行顺序。
多个数据库之间的建立关系,后续会补上。
既然要读写分离,那么数据库至少就要又两个,一个主数据库,负责读写,多个重数据库,负责读取。
既然是读写分离,为什么主数据库还要负责读和写呢?
在实际的应用场景种,
一、我写入数据的同时往往要读取一些基础数据。
二、数据在写入之后要读取出相关信息,读写分离再及时,也会产生延迟,这样程序上就会异常了。
所以,主数据库不仅需要写入的能力,还要有读取的能力。
笔者实用MySql数据库,jdbc.properties配置如下
mysqlM.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8 mysqlM.username=root mysqlM.password=root mysqlC.url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8 mysqlC.username=root mysqlC.password=root mysql.driverClassName=com.mysql.jdbc.Driver mysql.filters=stat mysql.maxActive=20 mysql.initialSize=1 mysql.maxWait=60000 mysql.minIdle=10 mysql.maxIdle=15 mysql.timeBetweenEvictionRunsMillis=60000 mysql.minEvictableIdleTimeMillis=300000 mysql.validationQuery=SELECT 'x' mysql.testWhileIdle=true mysql.testOnBorrow=false mysql.testOnReturn=false mysql.maxOpenPreparedStatements=20 mysql.removeAbandoned=true mysql.removeAbandonedTimeout=1800 mysql.logAbandoned=true
其中,mysqlM是主数据库,mysqlC是重数据库。
spring-dao.xml配置如下
<!-- 数据库基本信息配置 --> <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" abstract="true"> <property name="driverClassName" value="${mysql.driverClassName}" /> <property name="filters" value="${mysql.filters}" /> <property name="maxActive" value="${mysql.maxActive}" /> <property name="initialSize" value="${mysql.initialSize}" /> <property name="maxWait" value="${mysql.maxWait}" /> <property name="minIdle" value="${mysql.minIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${mysql.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${mysql.minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${mysql.validationQuery}" /> <property name="testWhileIdle" value="${mysql.testWhileIdle}" /> <property name="testOnBorrow" value="${mysql.testOnBorrow}" /> <property name="testOnReturn" value="${mysql.testOnReturn}" /> <property name="maxOpenPreparedStatements" value="${mysql.maxOpenPreparedStatements}" /> <property name="removeAbandoned" value="${mysql.removeAbandoned}" /> <property name="removeAbandonedTimeout" value="${mysql.removeAbandonedTimeout}" /> <property name="logAbandoned" value="${mysql.logAbandoned}" /> <property name="poolPreparedStatements" value="false" /> </bean> <!-- 主数据库 --> <bean id="dataSourceM" parent="parentDataSource"> <property name="url" value="${mysqlM.url}" /> <property name="username" value="${mysqlM.username}" /> <property name="password" value="${mysqlM.password}" /> </bean> <!-- 从数据库 --> <bean id="dataSourceC" parent="parentDataSource"> <property name="url" value="${mysqlC.url}" /> <property name="username" value="${mysqlC.username}" /> <property name="password" value="${mysqlC.password}" /> </bean> <!-- 启用CGliB --> <aop:aspectj-autoproxy/> <!--切面注入spring自定义标签--> <bean id="dsChangeAspect" class="com.org.util.DataSourceAspect"/> <!--动态数据源、主从库选择--> <bean id="dynamicDataSource" class="com.org.util.DynamicDataSource"> <property name="master" ref="dataSourceM"/> <property name="slaves"> <list> <ref bean="dataSourceC"/> </list> </property> </bean> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dynamicDataSource"></property> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSourceM" /> <!-- 启用基于注解的事务管理 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> <tx:method name="get*" propagation="REQUIRED" read-only="true" /> <tx:method name="add*" propagation="NESTED" isolation="REPEATABLE_READ" /> <tx:method name="init*" propagation="NESTED" isolation="REPEATABLE_READ" /> .... </tx:attributes> </tx:advice>
注解:
1、
<aop:aspectj-autoproxy/>
spring默认是不开启Aspect(自定义标签注入)模式的,需要手动开启。
2、
<bean id="dsChangeAspect" class="com.org.util.DataSourceAspect"/>
自定义类:DataSourceAspect,目的是创建自定义标签、绑定标签实现方法。代码如下:
package com.org.util; import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; /** * 有{@DataSourceChange}注解的方法,调用时会切换到指定的数据源 * @author zbr * @date 2016年12月28日 */ @Aspect public class DataSourceAspect { @Pointcut(value = "@annotation(com.org.util.DataSourceChange)") private void changeDS() { } @Around(value = "changeDS() ", argNames = "pjp") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { Object retVal = null; MethodSignature ms = (MethodSignature) pjp.getSignature(); Method method = ms.getMethod(); DataSourceChange annotation = method.getAnnotation(DataSourceChange.class); boolean selectedDataSource = false; try { if (null != annotation) { selectedDataSource = true; if (annotation.slave()) { DynamicDataSource.useSlave(); } else { DynamicDataSource.useMaster(); } } retVal = pjp.proceed(); } catch (Throwable e) { throw e; } finally { if (selectedDataSource) { DynamicDataSource.reset(); } } return retVal; } }
DataSourceChange 类
package com.org.util; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Inherited @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataSourceChange { boolean slave() default false; }
3、
com.org.util.DynamicDataSource
自定义类DynamicDataSource,动态数据源、主从库选择。这一切都要靠spring的AbstractRoutingDataSource,所以我们要继承AbstractRoutingDataSource。其实可以想一下,如果我们自己要实现读写分离,我们自己的思路是什么。
问题一、什么时候使用主库,什么时候使用重库。
问题二、什么时候去切换数据库。
第一个问题其实很好解释,主重的选择完全靠人为来决定,我们给方法、类或者接口加上自定义标签后,我们的程序就能判断出你期望使用的数据源。
第二个问题、很多人都会想到是在dao层查询的时候去选择dataSource,没错,spring也是这么想的,所以它提供了我们一个类:AbstractRoutingDataSource。
方法1:afterPropertiesSet,数据源初始化
方法2:determineCurrentLookupKey,翻译过来的意思是:根据key来选择数据源,就是说我们要访问数据库前,AbstractRoutingDataSource中的determineCurrentLookupKey方法,帮我们去选择dataSource,我们需要告诉spring我们要使用哪个数据源,也就是Key。
package com.org.util; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 动态数据源 * 配置主从数据源后,根据选择,返回对应的数据源。多个从库的情况下,会平均的分配从库,用于负载均衡。 * @author zbr * @date 2016年12月28日 */ public class DynamicDataSource extends AbstractRoutingDataSource{ private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class); private DataSource master; // 主库,只允许有一个 private List<DataSource> slaves; // 从库,允许有多个 private AtomicLong slaveCount = new AtomicLong(); private int slaveSize = 0; private Map<Object, Object> dataSources = new HashMap<Object, Object>(); private static final String DEFAULT = "master";//主数据库的Key private static final String SLAVE = "slave";//重数据库的key private static final ThreadLocal<LinkedList<String>> datasourceHolder = new ThreadLocal<LinkedList<String>>() { @Override protected LinkedList<String> initialValue() { return new LinkedList<String>(); } }; /** * 初始化 */ @Override public void afterPropertiesSet() { if (null == master) { throw new IllegalArgumentException("主数据库加载失败!"); } dataSources.put(DEFAULT, master); if (null != slaves && slaves.size() > 0) { for (int i = 0; i < slaves.size(); i++) { dataSources.put(SLAVE + (i + 1), slaves.get(i)); } slaveSize = slaves.size(); } this.setDefaultTargetDataSource(master); this.setTargetDataSources(dataSources); super.afterPropertiesSet(); } /** * 选择使用主库,并把选择放到当前ThreadLocal的栈顶 */ public static void useMaster() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("数据源使用:" + datasourceHolder.get()); } LinkedList<String> m = datasourceHolder.get(); m.offerFirst(DEFAULT); } /** * 选择使用从库,并把选择放到当前ThreadLocal的栈顶 */ public static void useSlave() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("数据源使用:" + datasourceHolder.get()); } LinkedList<String> m = datasourceHolder.get(); m.offerFirst(SLAVE); } /** * 重置当前栈 */ public static void reset() { LinkedList<String> m = datasourceHolder.get(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("重置数据源 {}", m); } if (m.size() > 0) { m.poll(); } } /** * 如果是选择使用从库,且从库的数量大于1,则通过取模来控制从库的负载, * 计算结果返回AbstractRoutingDataSource */ @Override protected Object determineCurrentLookupKey() { LinkedList<String> m = datasourceHolder.get(); String key = m.peekFirst() == null ? "" : m.peekFirst(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("当前数据源:" + key); } if (null != key) { if (DEFAULT.equals(key)) { return key; } else if (SLAVE.equals(key)) { if (slaveSize > 1) { long c = slaveCount.incrementAndGet(); c = c % slaveSize; return SLAVE + (c + 1); } else { return SLAVE + "1"; } } return null; } else { return null; } } public DataSource getMaster() { return master; } public List<DataSource> getSlaves() { return slaves; } public void setMaster(DataSource master) { this.master = master; } public void setSlaves(List<DataSource> slaves) { this.slaves = slaves; } }
以上就是读写分离的基本配置。
在我们需要读取重数据库的方法上加入注解:@DataSourceChange(slave = true)
在程序中断点一下,可以看到每个类的先后执行顺序。
多个数据库之间的建立关系,后续会补上。
相关文章推荐
- J2EE项目使用自定义注解实现基于SpringMVC + Mybatis + Mysql的读写分离
- spring+spring mvc+mybatis+mysql+easyui实现的分页
- SpringMVC+JdbcTemplate实现事务管理(插入操作需要返回自增字段)
- Android客户端+mysql+springmvc服务器端实现登陆的小案例
- Maven + Spring MVC+Mybatis + MySQL +AngularJS + Bootstrap 实现简单微博应用(三)前后台交互
- 使用Spring实现读写分离( MySQL实现主从复制)
- SpringMVC+Hibernate +MySql+ EasyUI实现CRUD(一)
- Maven + Spring MVC+Mybatis + MySQL +AngularJS + Bootstrap 实现简单微博应用(一)环境搭建
- SpringMVC Resetful+Hibernate+MySQL实现增删改查操作
- 使用Spring实现读写分离(MySQL实现主从复制)
- 使用Spring实现读写分离( MySQL实现主从复制)
- SpringMVC+Hibernate +MySql+ EasyUI实现POI导出Excel(二)
- Spring+Spring MVC+JDBCTemplate实现简单的用户管理功能
- spring-jdbc实现mysql读写分离
- 基于Spring MVC+Hibernate+Spring Security+Mysql 的B/S应用系统平台设计与实现
- SpringMVC+Hibernate +MySql+ EasyUI实现POI导出Excel(二)
- 未测试---- mysql+spring+mybatis实现数据库读写分离[代码配置]
- Springmvc+mybatis+mysql+ajax实现分页
- myeclipse 实现框架 spring+springmvc+springsecurity+myibatis+mysql用户认证和人员增删改查
- 转spring aop实现业务层mysql 读写分离