前段时间刚换了家新公司, 然后看项目代码里用了数据库读写分离的架构, 然后好奇扒了代码简单看了下, 总体来说就是运用 spring aop 切面方式来实现的. 看明白后就在自己的个人小项目里运用了下, 测试 OK, 所以下面总结下流程:
1, 首先定义一个数据源注解, 它有两个值, 一个对应写库 (主库), 一个对应读库 (从库)
- package com.jdd.ds;
- import java.lang.annotation.*;
- @Target({ElementType.METHOD, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface DataSource {
- String DATA_SOURCE_READ = "dataSourceRead";
- String DATA_SOURCE_WRITE = "dataSourceWrite";
- String name() default "dataSourceWrite";
- }
2, 在需要拦截的方法上加上该注解
- package com.jdd.service.impl;
- import com.jdd.dao.UserDao;
- import com.jdd.ds.DataSource;
- import com.jdd.pojo.User;
- import com.jdd.service.UserService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- @Service("userService")
- public class UserServiceImpl implements UserService{
- @Autowired
- private UserDao userDao;
- @DataSource(name=DataSource.DATA_SOURCE_READ)
- @Override
- public User getUserByNameAndPassword(String name, String password) {
- return userDao.getUserByNameAndPassword(name, password);
- }
- @DataSource(name=DataSource.DATA_SOURCE_WRITE)
- @Override
- public int insertUser(User user) {
- return userDao.insertUser(user);
- }
- }
3, 然后在配置文件里加上 aop 切面配置:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
- <!-- 扫描包加载 Service 实现类 -->
- <context:component-scan base-package="com.jdd.service"></context:component-scan>
- <bean id="dataSourceExchange" class="com.jdd.ds.DataSourceExchange"></bean>
- <aop:config>
- <aop:aspect ref="dataSourceExchange">
- <aop:around method="execute" pointcut="within(com.jdd.service.impl.*)"></aop:around>
- </aop:aspect>
- </aop:config>
- </beans>
4, 然后我们要在切面类里, 获取到方法上的 dataSource 注解里设置的值, 从而决定使用哪个数据源
- package com.jdd.ds;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.Signature;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.lang.reflect.Method;
- public class DataSourceExchange {
- private static Logger logger = LoggerFactory.getLogger(DataSourceExchange.class);
- public Object execute(ProceedingJoinPoint pjp){
- logger.info("DataSourceExchange==>");
- Object obj = null;
- Signature signature = pjp.getSignature();
- MethodSignature methodSignature = (MethodSignature)signature;
- Method targetMethod = methodSignature.getMethod();
- Method realMethod = null;
- try {
- realMethod = pjp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
- if(realMethod.isAnnotationPresent(DataSource.class)){
- DataSource datasource = (DataSource) realMethod.getAnnotation(DataSource.class);
- DataSourceContext.setDbType(datasource.name());
- logger.info(realMethod.getName() +"set dbtype==>"+datasource.name());
- }else{
- DataSourceContext.setDbType("dataSourceWrite");
- }
- obj = pjp.proceed();
- } catch (Throwable t) {
- t.printStackTrace();
- }
- logger.info(realMethod.getName()+"clear datatype==>");
- DataSourceContext.clearDbType();
- return obj;
- }
- }
在方法执行前, 设置具体数据源, 然后方法执行完后, 再清除掉该值.
注: 注意上面的通过反射获取业务方法的代码, 开始的时候用
- MethodSignature signature = (MethodSignature)pjp.getSignature();
- Method method = signature.getMethod();
发现通过这种方法获取的 method, 它是没有注解信息的. 后来在网上搜了下, 大概说是这种方法获取的是代理方法, 不是目标方法. 代理方法是不带注解信息的, 所以这块注意下.
5, 下面看下 DataSourceContext 类, 作用自然就是设置数据源上下文.
- package com.jdd.ds;
- public class DataSourceContext {
- public static final String DATA_SOURCE_READ = "dataSourceRead";
- public static final String DATA_SOURCE_WRITE = "dataSourceWrite";
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
- public DataSourceContext(){
- }
- public static void setDbType(String dbType){
- contextHolder.set(dbType);
- }
- public static String getDbType(){
- return (String)contextHolder.get();
- }
- public static void clearDbType(){
- contextHolder.remove();
- }
- }
6, 下面定义 MultipleDataSource 类, 继承与 spring 的 AbstractRoutingDataSource 类, 并重写它的 determineCurrentLookupKey 方法, 作用就是从上下文获取当前线程使用的数据源标识:
- package com.jdd.ds;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- public class MultipleDataSource extends AbstractRoutingDataSource{
- @Override
- protected Object determineCurrentLookupKey() {
- Object key = DataSourceContext.getDbType();
- if(key != null){
- this.logger.info("当前线程使用的数据源标识为 [" + key.toString() + "].");
- }
- return key;
- }
- }
7, 最后在 配置文件 applicationContext-mysql.xml 里 配上数据源
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
- xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
- http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
- <!-- 加载配置文件 -->
- <context:property-placeholder location="classpath:resource/*.properties" />
- <!-- 数据库连接池 -->
- <bean id="dataSourceWrite" class="com.alibaba.druid.pool.DruidDataSource"
- destroy-method="close">
- <property name="driverClassName" value="com.mysql.jdbc.Driver" />
- <property name="url" value="${url.write}" />
- <property name="username" value="${username.write}" />
- <property name="password" value="${password.write}" />
- <property name="maxActive" value="10" />
- <property name="minIdle" value="5" />
- </bean>
- <bean id="dataSourceRead" class="com.alibaba.druid.pool.DruidDataSource"
- destroy-method="close">
- <property name="driverClassName" value="com.mysql.jdbc.Driver" />
- <property name="url" value="${url.read}" />
- <property name="username" value="${username.read}" />
- <property name="password" value="${password.read}" />
- <property name="maxActive" value="10" />
- <property name="minIdle" value="5" />
- </bean>
- <bean id="multipleDataSource" class="com.jdd.ds.MultipleDataSource">
- <property name="defaultTargetDataSource" ref="dataSourceWrite" />
- <property name="targetDataSources">
- <map>
- <entry value-ref="dataSourceWrite" key="dataSourceWrite"></entry>
- <entry value-ref="dataSourceRead" key="dataSourceRead"></entry>
- </map>
- </property>
- </bean>
- <!-- sqlsessionFactory -->
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="configLocation" value="classpath:mybatis/SqlMapConfig.xml"></property>
- <property name="dataSource" ref="multipleDataSource"></property>
- <property name="mapperLocations" value="classpath:com/jdd/mapper/*.xml"></property>
- </bean>
- </beans>
8, 到这里和 spring 相关的配置基本就配完了, 其实后面还要再配置一下 mysql 的主从复制, 就是对写库的操作都同步到从库, 这样写库从库的数据才一致. 具体配置操作我就不写了, 可以自行到网上搜索.
来源: https://www.cnblogs.com/xiexin2015/p/8998647.html