Spring AOP 实现数据库读写分离

互联网架构演进中,数据库往往是一个瓶颈,对其进行优化是不可或缺的一环,数据库层面优化分为两个阶段:读写分离、分库分表。

今天要说的是 数据库读写分离技术,其原理就是一个Master数据库,多个Slave数据库。Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较少),而读取数据通常耗时比较长,占用数据库服务器的CPU较多,从而影响用户体验。
我们通常的做法就是把查询从主库中抽取出来,分发到多个从库上,减轻主库的压力。 采用读写分离技术的目标:有效减轻Master库的压力,又可以把用户查询数据的请求分发到不同的Slave库,从而保证系统的健壮性。

实现原理

在DAO的方法加上@DataSource注解,然后通过Spring AOP技术在运行时拦截DAO的方法,获取方法上的@DataSource注解值动态切换数据源。

实现

package com.bytebeats.spring4.sample.ds;

/**
 * 读写策略
 *
 * @author Ricky Fung
 * @create 2017-04-03 11:12
 */
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 2017-04-03 11:13
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    RoutingStrategy value();
}

Spring JDBC配置

MultipleDataSource源码如下:

package com.bytebeats.spring4.sample.ds;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源
 *
 * @author Ricky Fung
 * @create 2017-04-03 11:12
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {

        return DynamicRoutingContextHolder.getRouteStrategy();
    }
}

DynamicRoutingContextHolder

package com.bytebeats.spring4.sample.ds;

import org.springframework.util.Assert;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2017-04-03 11:14
 */
public class DynamicRoutingContextHolder {
    private static final ThreadLocal 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();
    }
}

最后,来看看 spring-dao.xml 配置:




    
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

    

    
    
        
        
        
        
        
    

    
        
        
        
        
    

    
    
        
            
                
                
            
        
        
        
    

    
    
        
    

    
    
    
    
        
    


Spring AOP配置

定义切面:

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 2017-04-03 16:59
 */
public class DBReadWriteInterceptor {

    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);
        }
    }
}

spring-aop.xml




    
    

    

    
        
            
            
        
    

如何使用

假设有一个订单库,现在要实现订单表的插入和查询,OrderDao接口定义如下:

package com.bytebeats.spring4.sample.dao;

import com.bytebeats.spring4.sample.domain.Order;
import java.util.List;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2017-04-03 12:12
 */
public interface OrderDao {

    long insert(Order order);

    List 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 2017-04-03 12:14
 */
@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 queryOrders(){

        return jdbcTemplate.query("SELECT * FROM tb_order", new RowMapper() {
            @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 实现数据库读写分离)