Spring AOP实现数据源切换(Spring Boot项目)

感谢网上诸多前辈的文章让我能写出通过Aop实现动态数据源切换,总结一下自己的学习,也是踩坑过来的,上代码。

PS:请注意看代码中的注释,注释方便你的理解。

1、创建application.yml文件,配置如下:(注意文件名千万别写错了)

#logging日志配置
logging:
  level:
    root: WARN
    org:
      springframework:
        web: DEBUG
##指向mapper的xml文件位置
mybatis:
    # 配置mapper的扫描,找到所有的mapper.xml映射文件
    mapperLocations: classpath:mybatis/mapper/*.xml
    # 加载全局的配置文件
    configLocation: mybatis/mybatis-config.xml
spring:
  datasource:
  ## master 数据源配置
    master:
      url: jdbc:mysql://***.**.**.**:3306/NIHAO?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: 123456
      driverClassName: com.mysql.jdbc.Driver
## cluster 数据源配置
    slave:
      url: jdbc:mysql://***.**.**.**:3306/NIHAO?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: 123456
      driverClassName: com.mysql.jdbc.Driver

2、配置pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>aop</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>aop</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- 加载properties文件依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- 阿里线程池以及json转换工具-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
        <!-- 字符串工具 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3、在resources文件夹下创建mapper文件,并创建mapper映射文件,例如创建UserMapping.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.aop.dao.UserDao">
    <!-- 创建用户(Create) -->
    <insert id="insertUser" parameterType="com.example.aop.entity.UserBean">
        insert into users(name,password) values(#{name},#{password})
    </insert>

    <!-- 删除用户(Delete) -->
    <update id="deleteUserById">
        delete from users where id=#{id}
    </update>

    <!-- 修改用户基本资料(Update) -->
    <update id="updateUserById" parameterType="com.example.aop.entity.UserBean">
        update users set name=#{name} where id=#{id}
    </update>

    <!-- 查询全部用户 -->
    <select id="getAllUser" resultType="com.example.aop.entity.UserBean">
        select * from users
    </select>
</mapper>

开始正式编码:

数据源代码:

4、创建动态数据源DynamicDataSource.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.annotation.Resource;
import javax.sql.DataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
    @Override
    protected Object determineCurrentLookupKey() {
        log.debug("数据源为:====", DynamicDataSourceHolder.getDataSourceKey());
        System.out.println("数据源为:===="+DynamicDataSourceHolder.getDataSourceKey());
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

5、创建动态数据源处理器DynamicDataSourceHolder.java

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DynamicDataSourceHolder {
    public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
    //使用线程安全的ThreadLocal记录当前线程数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    /**
     * 设置数据库key
     * @param key
     */
    public static void putDataSourceKey(String key){
        log.debug("切换到{}数据源", key);
        System.out.println("数据源:" + key);
        holder.set(key);
    }

    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey(){
        return holder.get();
    }

    /**
     * 删除数据源key
     */
    public static void removeDataSourceKey(){
        holder.remove();
    }
}

6、重写数据源默认配置,创建DataSourceConfig.java类

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置
 */
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {"com.example.aop.dao"}, sqlSessionFactoryRef   = "sqlSessionFactory")
public class DataSourceConfig {
    //  配置mapper的扫描,找到所有的mapper.xml映射文件
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;

    //  加载全局的配置文件
    @Value("${mybatis.configLocation}")
    private String configLocation;
    /**
     * 生成主数据源bean,通过@ConfigurationProperties导入数据源配置
     * @return
     */
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(){
        DruidDataSource masterDataSource = DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
        masterDataSource.setName("masterDataSource");
        return masterDataSource;
    }

    /**
     * 生成从数据源bean,通过@ConfigurationProperties导入数据源配置
     * @return
     */
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource(){
        DruidDataSource slaveDataSource = DataSourceBuilder.create().type(com.alibaba.druid.pool.DruidDataSource.class).build();
        slaveDataSource.setName("slaveDataSource");
        return slaveDataSource;
    }

    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap(2);
        dsMap.put("masterDataSource", masterDataSource());
        dsMap.put("slaveDataSource", slaveDataSource());
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }
    @Bean
    public PlatformTransactionManager txManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * @param dynamicDataSource
     * @return
     * @throws Exception
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
        //扫描mapper配置
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        //扫描mybatis配置文件
        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(configLocation) );
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * @param sqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory); // 使用上面配置的Factory
        return template;
    }

}

7、关键点创建DataSourceAspect.java

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;

@Aspect
@Order(1)
@Component
public class DataSourceAspect {
    //这两个必须与DatasourceConfig类中dataSource() 方法中hashmap的key一致,他通过key来判断数据源
    private static final String MASTER = "masterDataSource";
    private static final String SLAVE = "slaveDataSource";
    private static final String[] defaultSlaveMethod = new String[]{ "query", "find", "get" };
    //切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
    @Pointcut("execution( * com.example.aop.service.*.*(..))")
    public void dataSourcePointCut() {
    }

    @Before("dataSourcePointCut()")
    public void before(JoinPoint joinPoint) {
        // 获取到当前执行的方法名
        String methodName = joinPoint.getSignature().getName();
        boolean isSlave = false;
        isSlave = isSlave(methodName);
        System.out.println("是否从库:"+isSlave);
        if (isSlave) {
            // 标记为读库
            DynamicDataSourceHolder.putDataSourceKey(SLAVE);
        } else {
            // 标记为写库
            DynamicDataSourceHolder.putDataSourceKey(MASTER);
        }
    }

    //执行完切面后,将线程共享中的数据源名称清空
    @After("dataSourcePointCut()")
    public void after(JoinPoint joinPoint){
        System.out.println("执行完毕!");
        DynamicDataSourceHolder.removeDataSourceKey();
    }

    /**
     * 判断是否为读库
     *
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, defaultSlaveMethod);
    }

}

8、配置springboot启动类:

使用exclude即启动的时候不再加载springboot默认的数据源配置类

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class AopApplication {

    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
    }
}

通过以上的代码编写已经完成了动态数据源的切换,现在来验证:

实体类:

9、创建entity实体,UserBean.java

public class UserBean {
    private int id;
    private String name;
    private String password;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("\"id\":")
                .append(id);
        sb.append(",\"name\":\"")
                .append(name).append('\"');
        sb.append(",\"password\":\"")
                .append(password).append('\"');
        sb.append('}');
        return sb.toString();
    }
}

Dao层

10、创建dao,UserDao.java

import com.example.aop.entity.UserBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface UserDao {
    /**
     * @param userBean
     * @return
     */
    int insertUser(UserBean userBean);

    /**
     * @param id
     * @return
     */
    int deleteUserById(@Param("id") int id);

    /**
     * @param userBean
     * @return
     */
    int updateUserById(UserBean userBean);

    /**
     * @return
     */
    List<UserBean> getAllUser();
}

Service层:

11、创建service接口,UserService.java

package com.example.aop.service;

import com.example.aop.entity.UserBean;

import java.util.List;

public interface UserService {
    /**
     * @param userBean
     * @return
     */
    int insertUser(UserBean userBean);

    /**
     * @param id
     * @return
     */
    int deleteUserById(int id);

    /**
     * @param userBean
     * @return
     */
    int updateUserById(UserBean userBean);

    /**
     * @return
     */
    List<UserBean> getAllUser();
}

11、创建UserService实现类,UserServiceImpl.java

import com.example.aop.dao.UserDao;
import com.example.aop.entity.UserBean;
import com.example.aop.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.Max;
import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public int insertUser(UserBean userBean) {
        return userDao.insertUser(userBean);
    }

    @Override
    public int deleteUserById(int id) {
        return userDao.deleteUserById(id);
    }

    @Override
    public int updateUserById(UserBean userBean) {
        return userDao.updateUserById(userBean);
    }

    @Override
    public List<UserBean> getAllUser() {
        return userDao.getAllUser();
    }
}

测试类:

12、创建测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = AopApplication.class)
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testDynamicDatasource() {
        UserBean userBean = new UserBean();
        userBean.setName("tudou");
        userBean.setPassword("111111");
        userService.insertUser(userBean);
        System.out.println(userService.getAllUser());
        System.out.println(userService.insertUser(userBean));
        System.out.println(userService.getAllUser());
    }
}

猜你喜欢

转载自www.cnblogs.com/zhazhadequshi/p/9129203.html