zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    九个合理的个人所得税避税方案
    百度搜索技巧
    jsoup方法string转document
    java正则
    多线程之join方法
    java获取当前日期时间代码总结
    Error与Exception的区别,Java常见异常Execption总结
    Google Chrome调试js入门
    Resource temporarily unavailable用户的连接数设置的太小
    所有javax包
  • 原文地址:https://www.cnblogs.com/ouyanxia/p/7568908.html
Copyright © 2011-2022 走看看