Spring 动态数据源和AOP实现数据库读写分离
来源:互联网 发布:dts音效软件 编辑:程序博客网 时间:2024/04/30 12:42
互联网架构演进中 数据库优化是不可或缺的一环,数据库层面优化分为两个阶段:读写分离、分库分表。
今天要说的是 数据库读写分离技术,其原理就是一个Master数据库,多个Slave数据库。Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验。
我们通常的做法就是把查询从主库中抽取出来,分发到多个从库上,减轻主库的压力。
采用读写分离技术的目标:有效减轻Master库的压力,又可以把用户查询数据的请求分发到不同的Slave库,从而保证系统的健壮性。
实现原理
在DAO实现类的方法加上@RoutingDataSource注解,然后通过Spring AOP技术在运行时拦截DAO的方法,获取方法上的@RoutingDataSource注解值动态切换数据源。
代码实现
1、Spring动态数据源
1、spring-dao.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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-lazy-init="false"> <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" abstract="true" init-method="init" > <!-- 初始化连接大小 --> <property name="initialSize" value="2" /> <!-- 连接池最大使用连接数量 --> <property name="maxActive" value="10" /> <!-- 连接池最小空闲 --> <property name="minIdle" value="5" /> <!-- 获取连接最大等待时间 --> <property name="maxWait" value="30000" /> <!-- <property name="poolPreparedStatements" value="true" /> --> <!-- <property name="maxPoolPreparedStatementPerConnectionSize" value="33" /> --> <property name="validationQuery" value="SELECT 1" /> <property name="testOnBorrow" value="false" /> <property name="testOnReturn" value="false" /> <property name="testWhileIdle" value="true" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="25200000" /> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="true" /> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="1800" /> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="true" /> <!-- 监控数据库 --> <!-- <property name="filters" value="stat" /> --> <property name="filters" value="mergeStat" /> </bean> <!-- 配置数据源--> <bean id="masterDataSource" parent="parentDataSource"> <property name="url" value="#{jdbc['master.jdbc.url']}" /> <property name="username" value="#{jdbc['master.jdbc.username']}" /> <property name="password" value="#{jdbc['master.jdbc.password']}" /> <property name="driverClassName" value="#{jdbc['master.jdbc.driver']}" /> <property name="maxActive" value="15" /> </bean> <bean id="slaveDataSource" parent="parentDataSource"> <property name="url" value="#{jdbc['slave1.jdbc.url']}" /> <property name="username" value="#{jdbc['slave1.jdbc.username']}" /> <property name="password" value="#{jdbc['slave1.jdbc.password']}" /> <property name="driverClassName" value="#{jdbc['slave1.jdbc.driver']}" /> </bean> <!--动态数据源--> <bean id="dataSource" class="com.bytebeats.spring4.sample.ds.DynamicRoutingDataSource"> <property name="targetDataSources"> <map key-type="com.bytebeats.spring4.sample.ds.RoutingStrategy"> <entry key="WRITE" value-ref="masterDataSource"/> <entry key="READ" value-ref="slaveDataSource"/> </map> </property> <!-- 默认目标数据源为主库数据源 --> <property name="defaultTargetDataSource" ref="masterDataSource"/> </bean> <!--Spring JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注解方式配置事物 --> <tx:annotation-driven transaction-manager="transactionManager" /> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="masterDataSource" /> </bean></beans>
2、动态数据源实现
DynamicRoutingDataSource.java
package com.bytebeats.spring4.sample.ds;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * 动态数据源 * * @author Ricky Fung * @create 2016-12-30 11:12 */public class DynamicRoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicRoutingContextHolder.getRouteStrategy(); }}
DynamicRoutingContextHolder.java
package com.bytebeats.spring4.sample.ds;import org.springframework.util.Assert;/** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-12-30 11:14 */public class DynamicRoutingContextHolder { private static final ThreadLocal<RoutingStrategy> contextHolder = new ThreadLocal<>(); public static void setRouteStrategy(RoutingStrategy customerType) { Assert.notNull(customerType, "customerType cannot be null"); contextHolder.set(customerType); } public static RoutingStrategy getRouteStrategy() { return (RoutingStrategy) contextHolder.get(); } public static void clearRouteStrategy() { contextHolder.remove(); }}
RoutingStrategy.java
package com.bytebeats.spring4.sample.ds;/** * 读写策略 * * @author Ricky Fung * @create 2016-12-30 11:14 */public enum RoutingStrategy { WRITE, READ;}
数据源注解
package com.bytebeats.spring4.sample.annotation;import com.bytebeats.spring4.sample.ds.RoutingStrategy;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * ${DESCRIPTION} * * @author Ricky Fung * @date 2016-12-30 15:26 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RoutingDataSource { RoutingStrategy value();}
Spring AOP
1、spring-aop.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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置AOP --> <aop:aspectj-autoproxy /> <bean id="readWriteSeparateAspect" class="com.bytebeats.spring4.sample.aop.DBReadWriteSeparateAspect" /> <aop:config> <aop:aspect ref="readWriteSeparateAspect"> <aop:pointcut id="rw" expression="execution(* com.bytebeats.spring4.sample.dao.*.*(..))" /> <aop:before pointcut-ref="rw" method="before" /> </aop:aspect> </aop:config></beans>
2、切面类
package com.bytebeats.spring4.sample.aop;import com.bytebeats.spring4.sample.annotation.RoutingDataSource;import com.bytebeats.spring4.sample.ds.DynamicRoutingContextHolder;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.lang.reflect.Method;/** * ${DESCRIPTION} * * @author Ricky Fung * @date 2016-12-30 16:59 */public class DBReadWriteSeparateAspect { private final Logger logger = LoggerFactory.getLogger(getClass()); public void before(JoinPoint point) { Object target = point.getTarget(); String methodName = point.getSignature().getName(); Class<?> clazz = target.getClass(); logger.info("before class:{} method:{} execute", clazz.getName(), methodName); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes(); try { Method method = clazz.getMethod(methodName, parameterTypes); if (method != null && method.isAnnotationPresent(RoutingDataSource.class)) { RoutingDataSource data = method.getAnnotation(RoutingDataSource.class); DynamicRoutingContextHolder.setRouteStrategy(data.value()); logger.info("class:{} method:{} 切换数据源:{} 成功", clazz.getName(), methodName, data.value()); } } catch (Exception e) { logger.error("数据源切换切面异常", e); } }}
如何使用
假设有一个订单库,现在要实现订单表的插入和查询,IOrderDao接口定义如下:
package com.bytebeats.spring4.sample.dao;import com.bytebeats.spring4.sample.domain.Order;import java.util.List;/** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-12-30 11:15 */public interface IOrderDao { long insert(Order order); List<Order> queryOrders();}
我们期望insert路由到主库上,queryOrders路由到从库上,那么我们只需要在这两个方法上增加@RoutingDataSource注解即可,OrderDaoImpl 代码如下:
package com.bytebeats.spring4.sample.dao.impl;import com.bytebeats.spring4.sample.annotation.RoutingDataSource;import com.bytebeats.spring4.sample.dao.IOrderDao;import com.bytebeats.spring4.sample.domain.Order;import com.bytebeats.spring4.sample.ds.RoutingStrategy;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.RowMapper;import org.springframework.stereotype.Repository;import javax.annotation.Resource;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;/** * ${DESCRIPTION} * * @author Ricky Fung * @create 2016-12-30 11:16 */@Repository("orderDao")public class OrderDaoImpl implements IOrderDao { @Resource(name = "jdbcTemplate") private JdbcTemplate jdbcTemplate; @RoutingDataSource(RoutingStrategy.WRITE) @Override public long insert(Order order) { String sql = "INSERT INTO tb_order(customer_name,total_price,amount,address) VALUES (?,?,?,?)"; return jdbcTemplate.update(sql, order.getCustomerName(), order.getTotalPrice(), order.getAmount(), order.getAddress()); } @RoutingDataSource(RoutingStrategy.READ) @Override public List<Order> queryOrders(){ return jdbcTemplate.query("SELECT * FROM tb_order", new RowMapper<Order>() { @Override public Order mapRow(ResultSet rs, int i) throws SQLException { Order order = new Order(); order.setId(rs.getLong("id")); order.setCustomerName(rs.getString("customer_name")); order.setTotalPrice(rs.getDouble("total_price")); order.setAmount(rs.getInt("amount")); order.setAddress(rs.getString("address")); return order; } }); }}
源码下载
点此下载源码:https://github.com/TiFG/spring4-in-action/tree/master/spring-ch5-rw
- Spring 动态数据源和AOP实现数据库读写分离
- AOP的动态数据源配置,实现读写分离
- Spring实现动态数据源,支持动态添加、删除和设置权重及读写分离
- Spring实现动态数据源,支持动态添加、删除和设置权重及读写分离
- spring aop 实现读写分离
- spring aop实现读写分离:
- 使用Spring AOP实现MySQL数据库读写分离案例分析
- 使用Spring AOP实现MySQL数据库读写分离案例分析
- 使用Spring AOP实现MySQL数据库读写分离案例分析
- 使用Spring AOP实现MySQL数据库读写分离案例分析
- 使用Spring AOP实现MySQL数据库读写分离案例分析
- Spring AOP实现事务和主从读写分离
- 【spring】spring AOP实现mysql读写分离
- 使用spring的动态路由实现数据库读写分离【数据库读写分离(二) 】
- 使用spring的动态路由实现数据库读写分离【数据库读写分离(二) 】 .
- Spring AOP 实现主从读写分离
- Spring实现数据库读写分离
- spring实现数据库读写分离
- Java_java中FileWriter和FileReader类(基本使用方法)
- 消息转发机制
- ZCMU--1457-旅行
- leetcode钻研5 链表的插入排序
- The file “xxx” couldn’t be opened because you don’t have permission to view it
- Spring 动态数据源和AOP实现数据库读写分离
- 人生就像盖楼 - 再见2016 你好2017
- 面向对象与编程思想
- shadowsocks+vps实现google搜索
- 【解决方案】Ext.grid.CheckboxSelectionModel 实现锁住指定行不被选中
- 巧用Grep 命令
- 在win上开发scala所遇到的坑
- Hook
- Linux -mv命令的10个实用例子