在上一节中,我们讲述了多数据的情况:
1. 数据源不同(数据库厂商不同, 业务范围不同, 业务数据不同)
2. SQL mapper 文件不同,
3. mybatis + 数据方言不同
即最为简单的多数据, 将多个数据源叠加在一起,不同service---》dao--->sessionFactory;
如果上述的所有条件都相同,仅仅是数据的的多个拷贝情况,想做主备(读写分离),那我们当然可以用2套复制的代码和配置,但是显然不是最佳的实现方式。
这里,我们就讲解一下多数据源的读写分离,怎么实现。
首先,我们从读写分离的入口来看, 读read (对应JAVA/ getXXX, queryXXX), 写write(对应updateXXX, insertXXX), 如果能在这些方法执行时,自动切换到只读数据库/只写数据,那么就可以实现读写分离, 那我们自然可以想到spring 的AOP, 并以annotation的方式实现。
具体实现步骤如下:
1. 实现自定义的数据源注解: @DataSource("read") / @DataSource("write")
1 package com.robin.it.ds; 2 3 import java.lang.annotation.Retention; 4 import java.lang.annotation.Target; 5 import java.lang.annotation.RetentionPolicy; 6 import java.lang.annotation.ElementType; 7 /** 8 * RUNTIME 9 * 编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 10 * @author Robin.yi 11 * 12 */ 13 14 @Retention(RetentionPolicy.RUNTIME) 15 @Target(ElementType.METHOD) 16 public @interface DataSource { 17 String value(); 18 }
2. 实现一个数据库选择器ChooseDataSource
package com.robin.it.ds; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class ChooseDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return HandleDataSource.getDataSource(); } } ============ package com.robin.it.ds; public class HandleDataSource { public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String datasource) { holder.set(datasource); } public static String getDataSource() { return holder.get(); } }
3. 实现AOP 的的数据拦截,切换功能
package com.robin.it.ds; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; //@Aspect //@Component public class DataSourceAspect { //@Pointcut("execution(* com.apc.cms.service.*.*(..))") public void pointCut(){}; // @Before(value = "pointCut()") public void before(JoinPoint point) { Object target = point.getTarget(); System.out.println(target.toString()); String method = point.getSignature().getName(); System.out.println(method); Class<?>[] classz = target.getClass().getInterfaces(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); try { Method m = classz[0].getMethod(method, parameterTypes); System.out.println(m.getName()); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource data = m.getAnnotation(DataSource.class); System.out.println("==============================={}"+data.value()); HandleDataSource.putDataSource(data.value()); }else{ System.out.println("000000000000000000000"); } } catch (Exception e) { e.printStackTrace(); } } }
注意: 黄色高亮部分,利用反射读取 Interface的 的定义(包括annotation 的申明);动态读取参数,进行数据源切换;
4. 定义接口服务
package com.robin.it.permission.service; import com.robin.it.ds.DataSource; import com.robin.it.permission.module.User; /** * Created by Robin.yi on 2015-4-11. */ public interface IUserService { @DataSource("read") public User getUser(Integer userId); @DataSource("read") public User getUser(String account, String password); @DataSource("write") public void insertUser(User user); }
主意:上面是决定走读库/写库的语句: @DataSource("write") / @DataSource("read")
接口的实现无特别说明,此次省略。
5. 整个Spring 的applicationContent.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:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-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 http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd "> <description> This file is used to define the authority info. </description> <!-- 用于扫描 Spring 的各类annotation bean 变量: @autowired ; @Resource --> <context:annotation-config/> <!-- 用于扫描类中组建: 如: @Controller, @Service, @Component --> <context:component-scan base-package="com.robin.it.permission.*"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan> <tx:annotation-driven /> <bean id="configProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreResourceNotFound" value="false" /> <property name="locations"> <list> <!-- 这里支持多种寻址方式:classpath和file --> <value>classpath:/database.properties</value> <value>classpath:/config.properties</value> <!-- 推荐使用file的方式引入,这样可以将配置和代码分离 --> <!-- <value>file:/opt/demo/config/demo-message.properties</value>--> </list> </property> </bean> <util:properties id="props" location="classpath:/config.properties"/> <!-- MYSQL 配置 --> <bean id="readDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"><value>${mysql.jdbc.driverClassName}</value></property> <property name="url"><value>${mysql.jdbc.url}</value></property> <property name="username"><value>${mysql.jdbc.username}</value></property> <property name="password"><value>${mysql.jdbc.password}</value></property> </bean> <bean id="writeDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"><value>${mysql2.jdbc.driverClassName}</value></property> <property name="url"><value>${mysql2.jdbc.url}</value></property> <property name="username"><value>${mysql2.jdbc.username}</value></property> <property name="password"><value>${mysql2.jdbc.password}</value></property> </bean> <bean id="dataSource" class="com.robin.it.ds.ChooseDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- write --> <entry key="write" value-ref="writeDataSource"/> <!-- read --> <entry key="read" value-ref="readDataSource"/> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 指定sqlMapConfig总配置文件,订制的environment在spring容器中不在生效--> <property name="configLocation" value="classpath:mybatis-config-mysql.xml"/> <!--指定实体类映射文件,可以指定同时指定某一包以及子包下面的所有配置文件,mapperLocations和configLocation有一个即可, 当需要为实体类指定别名时,可指定configLocation属性,再在mybatis总配置文件中采用mapper引入实体类映射文件 --> <property name="mapperLocations"> <list> <value>classpath*:/mysqlmapper/*Mapper.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.robin.it.permission.dao" /> <!-- optional unless there are multiple session factories defined --> <property name="sqlSessionFactoryBeanName" value="sessionFactory" /> </bean> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean id="dataSourceAspect" class="com.robin.it.ds.DataSourceAspect" /> <aop:config> <aop:aspect id="c" ref="dataSourceAspect"> <aop:pointcut id="tx" expression="execution(* com.robin.it.permission.service.*.*(..))"/> <aop:before pointcut-ref="tx" method="before"/> </aop:aspect> </aop:config> </beans>
重点关注标为黄色的部分: 如何配置多数据源; 当知悉对应service [com.robin.it.permission.service.*.*(..)] , 的相关方法之前(aop:before), 执行DataSourceAspect切面方法;
数据库配置文件database.properties 参考,上一章节。
到此,已完成多数据源的,读写分离配置。