美文网首页java
Spring+MyBatis实现读写分离

Spring+MyBatis实现读写分离

作者: AbsurdOS | 来源:发表于2017-04-13 10:33 被阅读128次

    方案1

    通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactoryBean对象的mapperLocations属性制定两个读写数据源的配置文件。将所有读的操作配置在读文件中,所有写的操作配置在写文件中。

    • 优点:实现简单
    • 缺点:维护麻烦,需要对原有的xml文件进行重新修改,不支持多读,不易扩展
    • 实现方式
    <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"/>
        <property name="connectionProperties" value="config.decrypt=true" />
    </bean>
    
    <bean id="readDataSource" parent="abstractDataSource">
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${read.jdbc.url}"/>
        <property name="username" value="${read.jdbc.user}"/>
        <property name="password" value="${read.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${read.jdbc.initPoolSize}"/>
        <property name="minIdle" value="10"/>
        <property name="maxActive" value="${read.jdbc.maxPoolSize}"/>
    </bean>
    
    <bean id="writeDataSource" parent="abstractDataSource">
        <!-- 基本属性 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="10"/>
        <property name="maxActive" value="${write.jdbc.maxPoolSize}"/>
    </bean>
    
    <bean id="readSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="readDataSource"/>
        <property name="mapperLocations" value="classpath:mapper/read/*.xml"/>
    </bean>
    
    <bean id="writeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="writeDataSource"/>
        <property name="mapperLocations" value="classpath:mapper/write/*.xml"/>
    </bean>
    

    方案2

    通过Spring AOP在业务层实现读写分离,在DAO层调用前定义切面,利用Spring的AbstractRoutingDataSource解决多数据源的问题,实现动态选择数据源

    • 优点:通过注解的方法在DAO每个方法上配置数据源,原有代码改动量少,易扩展,支持多读
    • 缺点:需要在DAO每个方法上配置注解,人工管理,容易出错
    • 实现方式
    //定义枚举类型,读写 
    public enum DynamicDataSourceGlobal { 
        READ, WRITE; 
    }
    
    import java.lang.annotation.ElementType; 
    import java.lang.annotation.Retention; 
    import java.lang.annotation.RetentionPolicy; 
    import java.lang.annotation.Target;
    /** 
    * RUNTIME 
    * 定义注解 
    * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 
    * 
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface DataSource {
        public DynamicDataSourceGlobal value() default DynamicDataSourceGlobal.READ;
    }
    
    /** 
    * 本地线程设置和获取数据源信息 
    * Desc: 
    */
    public class DynamicDataSourceHolder { 
        private static final ThreadLocal<DynamicDataSourceGlobal> holder = new ThreadLocal<DynamicDataSourceGlobal>(); 
        public static void putDataSource(DynamicDataSourceGlobal dataSource){ 
            holder.set(dataSource); 
        } 
        public static DynamicDataSourceGlobal getDataSource(){ 
            return holder.get(); 
        } 
        public static void clearDataSource() { 
            holder.remove(); 
        }
    }
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 
    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;
    /** 
    * Created by IDEA 
    * Desc: 动态数据源实现读写分离 
    */
       public class DynamicDataSource 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(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
                if (this.readDataSources == null) {
                    readDataSourceSize = 0;
                } else {
                    for (int i = 0; i < readDataSources.size(); i++) {
                        targetDataSources.put(DynamicDataSourceGlobal.READ.name() + i, readDataSources.get(i));
                    }
                    readDataSourceSize = readDataSources.size();
                }
                setTargetDataSources(targetDataSources);
                super.afterPropertiesSet();
            }
    
            @Override 
            protected Object determineCurrentLookupKey() {
                DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
                if (dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE
                        || readDataSourceSize <= 0) {
                    return DynamicDataSourceGlobal.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;
            }
        }
    }
    
    
    import org.apache.log4j.Logger; 
    import org.aspectj.lang.JoinPoint; 
    import org.aspectj.lang.reflect.MethodSignature; 
    import java.lang.reflect.Method; 
    /** 
    * Created by IDEA 
    * Desc: 定义选择数据源切面 
    */
        public class DynamicDataSourceAspect {
            private static final Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);
    
            public void pointCut() {}
    
            public void before(JoinPoint point) {
                Object target = point.getTarget();
                String methodName = point.getSignature().getName();
                Class<?>[] clazz = target.getClass().getInterfaces();
                Class<?>[] parameterTypes =
                        ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
                try {
                    Method method = clazz[0].getMethod(methodName, parameterTypes);
                    if (method != null && method.isAnnotationPresent(DataSource.class)) {
                        DataSource data = method.getAnnotation(DataSource.class);
                        DynamicDataSourceHolder.putDataSource(data.value());
                    }
                } catch (Exception e) {
                    logger.error(String.format("Choose DataSource error, method:%s, msg:%s", methodName,
                            e.getMessage()));
                }
            }
    
            public void after(JoinPoint point) {
                DynamicDataSourceHolder.clearDataSource();
            }
        }
    
     <?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:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
    
    <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"/>
        <property name="connectionProperties" value="config.decrypt=true" />
    </bean>
    
    <bean id="dataSourceRead1" parent="abstractDataSource">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
        <!-- 基本属性 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="dataSourceRead2" parent="abstractDataSource">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
        <!-- 基本属性 url、user、password -->
        <property name="url" value="${read2.jdbc.url}"/>
        <property name="username" value="${read2.jdbc.user}"/>
        <property name="password" value="${read2.jdbc.password}"/>
        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${read2.jdbc.initPoolSize}"/>
        <property name="minIdle" value="${read2.jdbc.minPoolSize}"/>
        <property name="maxActive" value="${read2.jdbc.maxPoolSize}"/>
    </bean>
    
    <bean id="dataSourceWrite" parent="abstractDataSource">
        <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
        <!-- 基本属性 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.test.api.dao.datasource.DynamicDataSource">
        <property name="writeDataSource" ref="dataSourceWrite" />
        <property name="readDataSources">
            <list>
                <ref bean="dataSourceRead1" />
                <ref bean="dataSourceRead2" />
            </list>
        </property>
        <!--轮询方式-->
        <property name="readDataSourcePollPattern" value="1" />
        <property name="defaultTargetDataSource" ref="dataSourceWrite"/>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 针对myBatis的配置项 -->
    <!-- 配置sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    
    <!-- 配置扫描器 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 扫描包以及它的子包下的所有映射接口类 -->
        <property name="basePackage" value="com.test.api.dao.inte"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
    
    <!-- 配置数据库注解aop -->
    <bean id="dynamicDataSourceAspect" class="com.test.api.dao.datasource.DynamicDataSourceAspect" />
    <aop:config>
        <aop:aspect id="c" ref="dynamicDataSourceAspect">
            <aop:pointcut id="tx" expression="execution(* com.test.api.dao.inte..*.*(..))"/>
            <aop:before pointcut-ref="tx" method="before"/>
            <aop:after pointcut-ref="tx" method="after"/>
        </aop:aspect>
    </aop:config>
    <!-- 配置数据库注解aop -->
    </beans>
    

    方案3

    通过Mybatis的Plugin在业务层实现数据库读写分离,借鉴了LazyConnectionDataSourceProxy的connection Proxy实现方式, 在MyBatis创建Statement对象前通过拦截器选择真正的数据源,在拦截器中根据方法名称不同(select、update、insert、delete)选择数据源。

    • 优点:原有代码不变,支持多读,易扩展
    • 缺点:
    • 实现方式
    /** 
    * Created by IDEA 
    * Desc: 创建Connection代理接口 
    */ 
    public interface ConnectionProxy extends Connection { 
        /** 
        * 根据传入的读写分离需要的key路由到正确的connection 
        * @param key 数据源标识 
        * @return 
        */ 
        Connection getTargetConnection(String key); 
    }
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.logging.Logger;
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.jdbc.datasource.AbstractDataSource;
    import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
    import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
    import org.springframework.util.Assert;
    
    
    public abstract class AbstractDynamicDataSourceProxy extends AbstractDataSource implements InitializingBean {
        private List<Object> readDataSources;
        private List<DataSource> resolvedReadDataSources;
        private Object writeDataSource;
        private DataSource resolvedWriteDataSource;
        private int readDataSourcePollPattern = 0;
        private int readDsSize;
        private boolean defaultAutoCommit = true;
        private int defaultTransactionIsolation = Connection.TRANSACTION_READ_COMMITTED;
        public static final String READ = "read";
        public static final String WRITE = "write";
        private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
    
    
        @Override public Connection getConnection() throws SQLException {
            return (Connection) Proxy.newProxyInstance(
                    com.autohome.api.dealer.tuan.dao.rwmybatis.ConnectionProxy.class.getClassLoader(),
                    new Class[] { com.autohome.api.dealer.tuan.dao.rwmybatis.ConnectionProxy.class },
                    new RWConnectionInvocationHandler());
        }
    
    
        @Override public Connection getConnection(String username, String password) throws SQLException {
            return (Connection) Proxy.newProxyInstance(
                    com.autohome.api.dealer.tuan.dao.rwmybatis.ConnectionProxy.class.getClassLoader(),
                    new Class[] { com.autohome.api.dealer.tuan.dao.rwmybatis.ConnectionProxy.class },
                    new RWConnectionInvocationHandler(username, password));
        }
    
    
        public int getReadDsSize() {
            return readDsSize;
        }
    
    
        public List<DataSource> getResolvedReadDataSources() {
            return resolvedReadDataSources;
        }
    
    
        public void afterPropertiesSet() throws Exception {
            if (writeDataSource == null) {
                throw new IllegalArgumentException("Property 'writeDataSource' is required");
            }
            this.resolvedWriteDataSource = resolveSpecifiedDataSource(writeDataSource);
            resolvedReadDataSources = new ArrayList<DataSource>(readDataSources.size());
            for (Object item : readDataSources) {
                resolvedReadDataSources.add(resolveSpecifiedDataSource(item));
            }
            readDsSize = readDataSources.size();
        }
    
    
        protected DataSource determineTargetDataSource(String key) {
            Assert.notNull(this.resolvedReadDataSources, "DataSource router not initialized");
            if (WRITE.equals(key)) {
                return resolvedWriteDataSource;
            } else {
                return loadReadDataSource();
            }
        }
    
    
        public Logger getParentLogger() { // NOOP Just ignore
            return null;
        }
    
    
        /**
         * 获取真实的data source
         *
         * @param dataSource (jndi | real data source)
         * @return * @throws IllegalArgumentException
         */
        protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
            if (dataSource instanceof DataSource) {
                return (DataSource) dataSource;
            } else if (dataSource instanceof String) {
                return this.dataSourceLookup.getDataSource((String) dataSource);
            } else {
                throw new IllegalArgumentException(
                        "Illegal data source value - only [javax.sql.DataSource] and String supported: "
                                + dataSource);
            }
        }
    
    
        protected abstract DataSource loadReadDataSource();
    
    
        public void setReadDsSize(int readDsSize) {
            this.readDsSize = readDsSize;
        }
    
    
        public List<Object> getReadDataSources() {
            return readDataSources;
        }
    
    
        public void setReadDataSources(List<Object> readDataSources) {
            this.readDataSources = readDataSources;
        }
    
    
        public Object getWriteDataSource() {
            return writeDataSource;
        }
    
    
        public void setWriteDataSource(Object writeDataSource) {
            this.writeDataSource = writeDataSource;
        }
    
    
        public void setResolvedReadDataSources(List<DataSource> resolvedReadDataSources) {
            this.resolvedReadDataSources = resolvedReadDataSources;
        }
    
    
        public DataSource getResolvedWriteDataSource() {
            return resolvedWriteDataSource;
        }
    
    
        public void setResolvedWriteDataSource(DataSource resolvedWriteDataSource) {
            this.resolvedWriteDataSource = resolvedWriteDataSource;
        }
    
    
        public int getReadDataSourcePollPattern() {
            return readDataSourcePollPattern;
        }
    
    
        public void setReadDataSourcePollPattern(int readDataSourcePollPattern) {
            this.readDataSourcePollPattern = readDataSourcePollPattern;
        }
    
    
        /**
         * Invocation handler that defers fetching an actual JDBC Connection
         * until first creation of a Statement.
         */
        private class RWConnectionInvocationHandler implements InvocationHandler {
            private String username;
            private String password;
            private Boolean readOnly = Boolean.FALSE;
            private Integer transactionIsolation;
            private Boolean autoCommit;
            private boolean closed = false;
            private Connection target;
    
    
            public RWConnectionInvocationHandler() {
            }
    
    
            public RWConnectionInvocationHandler(String username, String password) {
                this();
                this.username = username;
                this.password = password;
            }
    
    
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // Invocation on ConnectionProxy interface coming in...
                if (method.getName().equals("equals")) {
                    // We must avoid fetching a target Connection for "equals". 
                    // Only consider equal when proxies are identical.
                    return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
                } else if (method.getName().equals("hashCode")) {
                    // We must avoid fetching a target Connection for "hashCode", 
                    // and we must return the same hash code even when the target 
                    // Connection has been fetched: use hashCode of Connection proxy. 
                    return new Integer(System.identityHashCode(proxy));
                } else if (method.getName().equals("getTargetConnection")) {
                    // Handle getTargetConnection method: return underlying connection. 
                    return getTargetConnection(method, args);
                }
                if (!hasTargetConnection()) {
                    // No physical target Connection kept yet -> 
                    // resolve transaction demarcation methods without fetching 
                    // a physical JDBC Connection until absolutely necessary. 
                    if (method.getName().equals("toString")) {
                        return "RW Routing DataSource Proxy";
                    } else if (method.getName().equals("isReadOnly")) {
                        return this.readOnly;
                    } else if (method.getName().equals("setReadOnly")) {
                        this.readOnly = (Boolean) args[0];
                        return null;
                    } else if (method.getName().equals("getTransactionIsolation")) {
                        if (this.transactionIsolation != null) {
                            return this.transactionIsolation;
                        }
                        return defaultTransactionIsolation;
                        // Else fetch actual Connection and check there, 
                        // because we didn't have a default specified. 
                    } else if (method.getName().equals("setTransactionIsolation")) {
                        this.transactionIsolation = (Integer) args[0];
                        return null;
                    } else if (method.getName().equals("getAutoCommit")) {
                        if (this.autoCommit != null)
                            return this.autoCommit;
                        return defaultAutoCommit;
                        // Else fetch actual Connection and check there, 
                        // because we didn't have a default specified.
                    } else if (method.getName().equals("setAutoCommit")) {
                        this.autoCommit = (Boolean) args[0];
                        return null;
                    } else if (method.getName().equals("commit")) { // Ignore: no statements created yet. 
                        return null;
                    } else if (method.getName().equals("rollback")) {
                        // Ignore: no statements created yet. 
                        return null;
                    } else if (method.getName().equals("getWarnings")) {
                        return null;
                    } else if (method.getName().equals("clearWarnings")) {
                        return null;
                    } else if (method.getName().equals("isClosed")) {
                        return (this.closed ? Boolean.TRUE : Boolean.FALSE);
                    } else if (method.getName().equals("close")) { // Ignore: no target connection yet. 
                        this.closed = true;
                        return null;
                    } else if (this.closed) {
                        // Connection proxy closed, without ever having fetched a 
                        // physical JDBC Connection: throw corresponding SQLException. 
                        throw new SQLException("Illegal operation: connection is closed");
                    }
                }
                // Target Connection already fetched, 
                // or target Connection necessary for current operation -> 
                // invoke method on target connection. 
                try {
                    return method.invoke(target, args);
                } catch (InvocationTargetException ex) {
                    throw ex.getTargetException();
                }
            }
    
    
            /**
             * Return whether the proxy currently holds a target Connection.
             */
            private boolean hasTargetConnection() {
                return (this.target != null);
            }
    
    
            /**
             * Return the target Connection, fetching it and initializing it if necessary.
             */
            private Connection getTargetConnection(Method operation, Object[] args) throws SQLException {
                if (this.target == null) {
                    String key = (String) args[0];
                    // No target Connection held -> fetch one. 
                    if (logger.isDebugEnabled()) {
                        logger.debug("Connecting to database for operation '" + operation.getName() + "'");
                    }
                    // Fetch physical Connection from DataSource. 
                    this.target = (this.username != null) ?
                            determineTargetDataSource(key).getConnection(this.username, this.password) :
                            determineTargetDataSource(key).getConnection();
                    // If we still lack default connection properties, check them now. 
                    // checkDefaultConnectionProperties(this.target); 
                    // Apply kept transaction settings, if any. 
                    if (this.readOnly.booleanValue()) {
                        this.target.setReadOnly(this.readOnly.booleanValue());
                    }
                    if (this.transactionIsolation != null) {
                        this.target.setTransactionIsolation(this.transactionIsolation.intValue());
                    }
                    if (this.autoCommit != null && this.autoCommit.booleanValue() != this.target
                            .getAutoCommit()) {
                        this.target.setAutoCommit(this.autoCommit.booleanValue());
                    }
                } else {
                    // Target Connection already held -> return it. 
                    if (logger.isDebugEnabled()) {
                        logger.debug(
                                "Using existing database connection for operation '" + operation.getName() + "'");
                    }
                }
                return this.target;
            }
        }
    }
    
    import javax.sql.DataSource;
    import java.util.concurrent.ThreadLocalRandom;
    import java.util.concurrent.atomic.AtomicLong;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    
    /**
     * Created by IDEA 
     * Desc:
     */
    public class DynamicRoutingDataSourceProxy extends AbstractDynamicDataSourceProxy {
        private AtomicLong counter = new AtomicLong(0);
        private static final Long MAX_POOL = Long.MAX_VALUE;
        private final Lock lock = new ReentrantLock();
    
    
        @Override protected DataSource loadReadDataSource() {
            int index = 1;
            if (getReadDataSourcePollPattern() == 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 % getReadDsSize());
            } else { //随机方式 
                index = ThreadLocalRandom.current().nextInt(0, getReadDsSize());
            }
            return getResolvedReadDataSources().get(index);
        }
    }
    
    import org.apache.ibatis.executor.statement.RoutingStatementHandler;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.*;
    
    import java.sql.Connection;
    import java.util.Properties;
    
    
    /**
     * 拦截器
     */
    @Intercepts({ 
        @Signature(type = StatementHandler.class, method = "prepare", args = {
            Connection.class }) }) 
    public class DynamicPlugin implements Interceptor {
        public Object intercept(Invocation invocation) throws Throwable {
            Connection conn = (Connection) invocation.getArgs()[0]; //如果是采用了我们代理,则路由数据源 
            if (conn instanceof com.autohome.api.dealer.tuan.dao.rwmybatis.ConnectionProxy) {
                StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
                MappedStatement mappedStatement = null;
                if (statementHandler instanceof RoutingStatementHandler) {
                    StatementHandler delegate =
                            (StatementHandler) ReflectionUtils.getFieldValue(statementHandler, "delegate");
                    mappedStatement =
                            (MappedStatement) ReflectionUtils.getFieldValue(delegate, "mappedStatement");
                } else {
                    mappedStatement =
                            (MappedStatement) ReflectionUtils.getFieldValue(statementHandler, "mappedStatement");
                }
                String key = AbstractDynamicDataSourceProxy.WRITE;
                if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
                    key = AbstractDynamicDataSourceProxy.READ;
                } else {
                    key = AbstractDynamicDataSourceProxy.WRITE;
                }
                ConnectionProxy connectionProxy = (ConnectionProxy) conn;
                connectionProxy.getTargetConnection(key);
            }
            return invocation.proceed();
        }
    
    
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
    
        public void setProperties(Properties properties) { //NOOP 
        }
    }
    
    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.logging.LogFactory;
    
    import java.lang.reflect.*;
    
    
    public class ReflectionUtils {
        private static final Log logger = LogFactory.getLog(ReflectionUtils.class);
    
    
        /**
         * 直接设置对象属性值,无视private/protected修饰符,不经过setter函数.
         */
        public static void setFieldValue(final Object object, final String fieldName, final Object value) {
            Field field = getDeclaredField(object, fieldName);
            if (field == null)
                throw new IllegalArgumentException(
                        "Could not find field [" + fieldName + "] on target [" + object + "]");
            makeAccessible(field);
            try {
                field.set(object, value);
            } catch (IllegalAccessException e) {
            }
        }
    
    
        /**
         * 直接读取对象属性值,无视private/protected修饰符,不经过getter函数.
         */
        public static Object getFieldValue(final Object object, final String fieldName) {
            Field field = getDeclaredField(object, fieldName);
            if (field == null)
                throw new IllegalArgumentException(
                        "Could not find field [" + fieldName + "] on target [" + object + "]");
            makeAccessible(field);
            Object result = null;
            try {
                result = field.get(object);
            } catch (IllegalAccessException e) {
            }
            return result;
        }
    
    
        /**
         * 直接调用对象方法,无视private/protected修饰符.
         */
        public static Object invokeMethod(final Object object, final String methodName,
                final Class<?>[] parameterTypes, final Object[] parameters) throws InvocationTargetException {
            Method method = getDeclaredMethod(object, methodName, parameterTypes);
            if (method == null)
                throw new IllegalArgumentException(
                        "Could not find method [" + methodName + "] on target [" + object + "]");
            method.setAccessible(true);
            try {
                return method.invoke(object, parameters);
            } catch (IllegalAccessException e) {
            }
            return null;
        }
    
    
        /**
         * 循环向上转型,获取对象的DeclaredField.
         */
        protected static Field getDeclaredField(final Object object, final String fieldName) {
            for (Class<?> superClass = object.getClass();
                 superClass != Object.class; superClass = superClass.getSuperclass()) {
                try {
                    return superClass.getDeclaredField(fieldName);
                } catch (NoSuchFieldException e) {
                }
            }
            return null;
        }
    
    
        /**
         * 循环向上转型,获取对象的DeclaredField.
         */
        protected static void makeAccessible(final Field field) {
            if (!Modifier.isPublic(field.getModifiers()) || !Modifier
                    .isPublic(field.getDeclaringClass().getModifiers())) {
                field.setAccessible(true);
            }
        }
    
    
        /**
         * 循环向上转型,获取对象的DeclaredMethod.
         */
        protected static Method getDeclaredMethod(Object object, String methodName, Class<?>[] parameterTypes) {
            for (Class<?> superClass = object.getClass();
                 superClass != Object.class; superClass = superClass.getSuperclass()) {
                try {
                    return superClass.getDeclaredMethod(methodName, parameterTypes);
                } catch (NoSuchMethodException e) {
                }
            }
            return null;
        }
    
    
        /**
         * 通过反射,获得Class定义中声明的父类的泛型参数的类型. * eg. * public UserDao extends HibernateDao<User> * * @param clazz The class to introspect * @return the first generic declaration, or Object.class if cannot be determined
         */
        @SuppressWarnings("unchecked") public static <T> Class<T> getSuperClassGenricType(final Class clazz) {
            return getSuperClassGenricType(clazz, 0);
        }
    
    
        /**
         * 通过反射,获得Class定义中声明的父类的泛型参数的类型. * eg. * public UserDao extends HibernateDao<User> * * @param clazz The class to introspect * @return the first generic declaration, or Object.class if cannot be determined
         */
        @SuppressWarnings("unchecked") public static Class getSuperClassGenricType(final Class clazz,
                final int index) {
            Type genType = clazz.getGenericSuperclass();
            if (!(genType instanceof ParameterizedType)) {
                logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
                return Object.class;
            }
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
            if (index >= params.length || index < 0) {
                logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
                        + params.length);
                return Object.class;
            }
            if (!(params[index] instanceof Class)) {
                logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
                return Object.class;
            }
            return (Class) params[index];
        }
    
    
        /**
         * 将反射时的checked exception转换为unchecked exception.
         */
        public static IllegalArgumentException convertToUncheckedException(Exception e) {
            if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
                    || e instanceof NoSuchMethodException)
                return new IllegalArgumentException("Refelction Exception.", e);
            else
                return new IllegalArgumentException(e);
        }
    }
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <plugins> 
            <plugin interceptor="com.test.api.dao.mybatis.DynamicPlugin"> </plugin> 
        </plugins> 
    </configuration>
    
    <?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:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
    
        <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"/>
            <property name="connectionProperties" value="config.decrypt=true" />
        </bean>
    
        <bean id="dataSourceRead1" parent="abstractDataSource">
            <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
            <!-- 基本属性 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="dataSourceRead2" parent="abstractDataSource">
            <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
            <!-- 基本属性 url、user、password -->
            <property name="url" value="${read2.jdbc.url}"/>
            <property name="username" value="${read2.jdbc.user}"/>
            <property name="password" value="${read2.jdbc.password}"/>
            <!-- 配置初始化大小、最小、最大 -->
            <property name="initialSize" value="${read2.jdbc.initPoolSize}"/>
            <property name="minIdle" value="${read2.jdbc.minPoolSize}"/>
            <property name="maxActive" value="${read2.jdbc.maxPoolSize}"/>
        </bean>
    
        <bean id="dataSourceWrite" parent="abstractDataSource">
            <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
            <!-- 基本属性 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.test.api.dao.datasource.DynamicRoutingDataSourceProxy">
            <property name="writeDataSource" ref="dataSourceWrite" />
            <property name="readDataSources">
                <list>
                    <ref bean="dataSourceRead1" />
                    <ref bean="dataSourceRead2" />
                </list>
            </property>
            <!--轮询方式-->
            <property name="readDataSourcePollPattern" value="1" />
        </bean>
    
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- 针对myBatis的配置项 -->
        <!-- 配置sqlSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
            <property name="dataSource" ref="dataSource"/>
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>
            <property name="configLocation" value="classpath:mybatis-plugin-config.xml" />
        </bean>
    
        <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
            <constructor-arg ref="sqlSessionFactory" />
        </bean>
        <!-- 通过扫描的模式,扫描目录下所有的mapper, 根据对应的mapper.xml为其生成代理类-->
        <bean id="mapper" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.test.api.dao.inte" />
            <property name="sqlSessionTemplate" ref="sqlSessionTemplate"></property>
        </bean>
    
    </beans>
    
    

    方案4

    Spring路由选择+MyBatis拦截器实现
    如果你的后台结构是spring+mybatis,可以通过spring的AbstractRoutingDataSource和mybatis Plugin拦截器实现非常友好的读写分离,原有代码不需要任何改变。推荐第四种方案

    package com.autohome.api.dealer.util.rwdb;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    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;
    
    
    /**
     * Created by IDEA 
     * Desc: 动态数据源实现读写分离
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        private Object writeDataSource;//写数据源 
        private Object readDataSource; //读数据源 
    
    
        @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(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
            targetDataSources.put(DynamicDataSourceGlobal.READ.name(), readDataSource);
            setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
    
        @Override protected Object determineCurrentLookupKey() {
            DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
            if (dynamicDataSourceGlobal == null || dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE) {
                return DynamicDataSourceGlobal.WRITE.name();
            }
            return DynamicDataSourceGlobal.READ.name();
        }
    
    
        public void setWriteDataSource(Object writeDataSource) {
            this.writeDataSource = writeDataSource;
        }
    
    
        public Object getWriteDataSource() {
            return writeDataSource;
        }
    
    
        public Object getReadDataSource() {
            return readDataSource;
        }
    
    
        public void setReadDataSource(Object readDataSource) {
            this.readDataSource = readDataSource;
        }
    }
    
    package com.autohome.api.dealer.util.rwdb; 
    /** 
    * Created by IDEA 
    * Desc: 
    */ 
    public enum DynamicDataSourceGlobal { 
        READ, WRITE; 
    }
    
    package com.autohome.api.dealer.util.rwdb;
    
    /**
     * Created by IDEA 
     * Desc:
     */
    public final class DynamicDataSourceHolder {
        private static final ThreadLocal<DynamicDataSourceGlobal> holder =
                new ThreadLocal<DynamicDataSourceGlobal>();
    
    
        private DynamicDataSourceHolder() {
        }
    
    
        public static void putDataSource(DynamicDataSourceGlobal dataSource) {
            holder.set(dataSource);
        }
    
    
        public static DynamicDataSourceGlobal getDataSource() {
            return holder.get();
        }
    
    
        public static void clearDataSource() {
            holder.remove();
        }
    }
    
    package com.autohome.api.dealer.util.rwdb;
    
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    
    
    /**
     * Created by IDEA 
     * Desc:
     */
    public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
        /**
         * 只读事务到读库,读写事务到写库 
         * @param transaction 
         * @param definition
         */
        @Override protected void doBegin(Object transaction, TransactionDefinition definition) { //设置数据源 
            boolean readOnly = definition.isReadOnly();
            if (readOnly) {
                DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
            } else {
                DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
            }
            super.doBegin(transaction, definition);
        }
    
    
        /**
         * 清理本地线程的数据源 
         * @param transaction
         */
        @Override protected void doCleanupAfterCompletion(Object transaction) {
            super.doCleanupAfterCompletion(transaction);
            DynamicDataSourceHolder.clearDataSource();
        }
    }
    
    package com.autohome.api.dealer.util.rwdb;
    
    import org.apache.commons.lang3.StringUtils;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    import java.util.Locale;
    import java.util.Map;
    import java.util.Properties;
    import java.util.concurrent.ConcurrentHashMap;
    
    
    /**
     * Created by IDEA 
     * Desc:
     */
    @Intercepts({
            @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
            @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                                                                         RowBounds.class,
                                                                         ResultHandler.class }) }) public class DynamicPlugin
            implements Interceptor {
        protected static final Logger logger = LoggerFactory.getLogger(DynamicPlugin.class);
        private static final String REGEX = ".*insert.*|.*delete.*|.*update.*|.*drop.*";
        private static final Map<String, DynamicDataSourceGlobal> cacheMap = new ConcurrentHashMap<>();
    
        @Override public Object intercept(Invocation invocation) throws Throwable {
            boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
            if (!synchronizationActive) {
                Object[] objects = invocation.getArgs();
                MappedStatement ms = (MappedStatement) objects[0];
                DynamicDataSourceGlobal dynamicDataSourceGlobal = null;
                if ((dynamicDataSourceGlobal = cacheMap.get(ms.getId())) == null) { //读方法 
                    if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                        BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        if (sql.matches(REGEX)) {
                            dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
                        } else {
                            dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
                        }
                    } else {
                        dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(),
                                dynamicDataSourceGlobal.name(), ms.getSqlCommandType().name());
                    }
                    cacheMap.put(ms.getId(), dynamicDataSourceGlobal);
                }
                DynamicDataSourceHolder.putDataSource(dynamicDataSourceGlobal);
            }
            return invocation.proceed();
        }
    
        @Override public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            } else {
                return target;
            }
        }
    
        @Override public void setProperties(Properties properties) { // 
        }
    }
    
    <?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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">
    
        <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"/>
            <!-- 基本属性 url、user、password -->
            <property name="url" value="${autoSuper.jdbc.url}"/>
            <!-- 配置初始化大小、最小、最大 -->
            <property name="initialSize" value="${autoSuper.jdbc.initPoolSize}"/>
            <property name="minIdle" value="3"/>
            <property name="maxActive" value="${autoSuper.jdbc.maxPoolSize}"/>
            <!-- 配置获取连接等待超时的时间 -->
            <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"/>
            <property name="connectionProperties" value="config.decrypt=true" />
        </bean>
    
        <bean id="autoSuperDataSourceWrite" parent="abstractDataSource">
            <property name="username" value="${autoSuper.jdbc.user}"/>
            <property name="password" value="${autoSuper.jdbc.password}"/>
        </bean>
    
        <bean id="autoSuperDataSourceRead" parent="abstractDataSource">
            <property name="username" value="${autoSuper.jdbc.readOnly.user}"/>
            <property name="password" value="${autoSuper.jdbc.readOnly.password}"/>
        </bean>
    
        <bean id="autoSuperDataSource"  class="com.autohome.api.dealer.util.rwdb.DynamicDataSource">
            <property name="writeDataSource"  ref="autoSuperDataSourceWrite"></property>
            <property name="readDataSource" ref="autoSuperDataSourceRead"></property>
        </bean>
    
        <tx:annotation-driven transaction-manager="autoSuperTransactionManager"/>
    
        <bean id="autoSuperTransactionManager" class="com.autohome.api.dealer.util.rwdb.DynamicDataSourceTransactionManager">
            <property name="dataSource" ref="autoSuperDataSource"/>
        </bean>
    
        <!-- 针对myBatis的配置项 -->
        <!-- 配置sqlSessionFactory -->
        <bean id="autoSuperSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 实例化sqlSessionFactory时需要使用上述配置好的数据源以及SQL映射文件 -->
            <property name="dataSource" ref="autoSuperDataSource"/>
            <property name="configLocation" value="classpath:mybatis-plugin-config.xml" />
            <!-- 自动扫描me/gacl/mapping/目录下的所有SQL映射的xml文件, 省掉Configuration.xml里的手工配置
            value="classpath:me/gacl/mapping/*.xml"指的是classpath(类路径)下me.gacl.mapping包中的所有xml文件
            UserMapper.xml位于me.gacl.mapping包下,这样UserMapper.xml就可以被自动扫描
             -->
            <property name="mapperLocations" value="classpath:mapper/autoSuper/*.xml"/>
            <property name="typeAliasesPackage" value="com.autohome.api.supercall.dao.domain"></property>
        </bean>
    
        <!-- 配置扫描器 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 扫描包以及它的子包下的所有映射接口类 -->
            <property name="basePackage" value="com.autohome.api.supercall.dao.inte.autoSuper"/>
            <property name="sqlSessionFactoryBeanName" value="autoSuperSqlSessionFactory"/>
        </bean>
    </beans>
    
    
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <plugins>
            <plugin interceptor="com.autohome.api.dealer.util.rwdb.DynamicPlugin">
            </plugin>
        </plugins>
    
    </configuration>
    

    相关文章

      网友评论

        本文标题:Spring+MyBatis实现读写分离

        本文链接:https://www.haomeiwen.com/subject/uttbattx.html