Spring Boot 使用AOP实现多个数据库源的读写分离

1、首先引入Maven依赖

            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <!--使用AspectJ方式注解需要相应的包 -->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>${aspectj.version}</version>
            </dependency>
            <dependency>  
                <groupId>org.springframework.boot</groupId>  
                <artifactId>spring-boot-starter-aop</artifactId>
                <version>1.3.0.RELEASE</version>
            </dependency>                

2.自定义注解类(annotation)

@Target({ElementType.METHOD, ElementType.TYPE})
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
这里
ElementType.METHOD用于描述方法,ElementType.TYPE用于描述类、接口(包括注解类型) 或enum声明

@Retention(RetentionPolicy.RUNTIME)生命周期
RetentionPolicy.RUNTIME表示运行时有效
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {//定义注解的名称为ReadOnlyConnection
}

3、使用aop定义环绕(@Around)切面(即在运行切面方法的前后执行)

import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import com.oadmin.superapp.config.DbContextHolder;
import com.oadmin.superapp.config.DbType;

@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {
    private static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
    //@Pointcut("within(com.oadmin.superapp.manager.impl..*) && @annotation(com.oadmin.superapp.filter.ReadOnlyConnection)")  
    @Pointcut("execution(* com.oadmin.superapp.manager.impl..*(..)) ")  //定义切面的范围
    public void managerMethodPointcut(){}  
    @Around("managerMethodPointcut() and @annotation(readOnlyConnection)")
    public Object proceed(ProceedingJoinPoint proceedingJoinPoint,ReadOnlyConnection readOnlyConnection) throws Throwable{
        try {
            logger.info("set database connection to read only");
            DbContextHolder.setDbType(DbType.READ);
            Object result = proceedingJoinPoint.proceed();
            return result;
        } finally {
            DbContextHolder.clearDbType();
            logger.info("restore database connection");
        }
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

4.定义DbContextHolder类,get()、set方法,对DbType赋值

public class DbContextHolder {

    private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();
    public static void setDbType(DbType dbType) {
        if (dbType == null) {
            throw new NullPointerException();
        }
        contextHolder.set(dbType);
    }

    public static DbType getDbType() {
        return (DbType) contextHolder.get();
    }
    public static void clearDbType() {
        contextHolder.remove();
    }
}

5、定义Dbtype的类型(枚举类enum),即write和read两种类型

public enum DbType {
    WRITE, READ,
}

6、连接数据源(DataSource)

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
    private Object writeDataSource; // 写数据源
    private List<Object> readDataSources; // 多个读数据源
    private int readDataSourceSize; // 读数据源个数
    private int readDataSourcePollPattern = 0; // 获取读数据源方式,0:随机,1:轮询
    private AtomicLong counter = new AtomicLong(0);
    private static final Long MAX_POOL = Long.MAX_VALUE;
    private final Lock lock = new ReentrantLock();
    @Override
    public void afterPropertiesSet() {
        if (this.writeDataSource == null) {
            throw new IllegalArgumentException("Property 'writeDataSource' is required");
        }
        setDefaultTargetDataSource(writeDataSource);
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DbType.WRITE.name(), writeDataSource);
        if (this.readDataSources == null) {
            readDataSourceSize = 0;
        } else {
            for (int i = 0; i < readDataSources.size(); i++) {
                targetDataSources.put(DbType.READ.name() + i, readDataSources.get(i));
            }
            readDataSourceSize = readDataSources.size();
        }
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }
    @Override
    protected Object determineCurrentLookupKey() {

        DbType dynamicDataSourceGlobal = DbContextHolder.getDbType();

        if (dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DbType.WRITE || readDataSourceSize <= 0) {
            return DbType.WRITE.name();
        }
        int index = 1;

        if (readDataSourcePollPattern == 1) {
            // 轮询方式
            long currValue = counter.incrementAndGet();
            if ((currValue + 1) >= MAX_POOL) {
                try {
                    lock.lock();
                    if ((currValue + 1) >= MAX_POOL) {
                        counter.set(0);
                    }
                } finally {
                    lock.unlock();
                }
            }
            index = (int) (currValue % readDataSourceSize);
        } else {
            // 随机方式
            index = ThreadLocalRandom.current().nextInt(0, readDataSourceSize);
        }
        return dynamicDataSourceGlobal.name() + index;
    }

    public void setWriteDataSource(Object writeDataSource) {
        this.writeDataSource = writeDataSource;
    }

    public void setReadDataSources(List<Object> readDataSources) {
        this.readDataSources = readDataSources;
    }
    public void setReadDataSourcePollPattern(int readDataSourcePollPattern) {
        this.readDataSourcePollPattern = readDataSourcePollPattern;
    }
}

7、数据源配置(这里是在mybatis的配置文件配置数据源)

    <bean id="abstractDataSource" abstract="true"
        class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
        destroy-method="close">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="60000" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="60000" />
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize"
            value="20" />
        <property name="filters" value="config" />
    </bean>

    <bean id="dataSourceRead1" parent="abstractDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${read1.jdbc.url}" />
        <property name="username" value="${read1.jdbc.user}" />
        <property name="password" value="${read1.jdbc.password}" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${read1.jdbc.initPoolSize}" />
        <property name="minIdle" value="${read1.jdbc.minPoolSize}" />
        <property name="maxActive" value="${read1.jdbc.maxPoolSize}" />
    </bean>

    <bean id="dataSourceWrite" parent="abstractDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${write.jdbc.url}" />
        <property name="username" value="${write.jdbc.user}" />
        <property name="password" value="${write.jdbc.password}" />
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${write.jdbc.initPoolSize}" />
        <property name="minIdle" value="${write.jdbc.minPoolSize}" />
        <property name="maxActive" value="${write.jdbc.maxPoolSize}" />
    </bean>
  <!--配置了一个读一个学的资源-->
    <bean id="dataSource" class="com.oadmin.superapp.config.RoutingDataSource">
        <property name="writeDataSource" ref="dataSourceWrite" />
        <property name="readDataSources">
            <list>
                <ref bean="dataSourceRead1" />
            </list>
        </property>
        <!--轮询方式 -->
        <property name="readDataSourcePollPattern" value="1" />
        <property name="defaultTargetDataSource" ref="dataSourceWrite" />
    </bean>
   <!--配置事务管理--> <tx:annotation-driven transaction-manager="transactionManager" order="100" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>

 8、properties文件文件配置

# Slave DataSource
#server.port=8011
server.contextPath=/oadmin
read1.jdbc.url=jdbc:postgresql://172.16.1.200:54321/database
read1.jdbc.user=postgres
read1.jdbc.password=123456
read1.jdbc.initPoolSize=1
read1.jdbc.minPoolSize=1
read1.jdbc.maxPoolSize=10
# Master DataSource
write.jdbc.url=jdbc:postgresql://172.16.1.200:54321/database
write.jdbc.user=postgres
write.jdbc.password=123456
write.jdbc.initPoolSize=1
write.jdbc.minPoolSize=1
write.jdbc.maxPoolSize=10

 

posted on 2017-09-21 15:41  依米欧  阅读(1372)  评论(0编辑  收藏  举报