SpringBoot 2.2.5 使用AOP方式配置多數(shù)據(jù)源動(dòng)態(tài)切換匿值,并支持事務(wù),并解決內(nèi)部方法調(diào)用時(shí)AOP切面失效的問題

說明

  1. 隨著應(yīng)用用戶數(shù)量的增加赂摆,相應(yīng)的并發(fā)請(qǐng)求的數(shù)量也會(huì)跟著不斷增加挟憔,慢慢地,單個(gè)數(shù)據(jù)庫已經(jīng)沒有辦法滿足我們頻繁的數(shù)據(jù)庫操作請(qǐng)求了烟号。
  2. 在某些場(chǎng)景下绊谭,我們可能會(huì)需要配置多個(gè)數(shù)據(jù)源,使用多個(gè)數(shù)據(jù)源(例如實(shí)現(xiàn)數(shù)據(jù)庫的讀寫分離)來緩解系統(tǒng)的壓力等汪拥,同樣的达传,SpringBoot官方提供了相應(yīng)的實(shí)現(xiàn)來幫助開發(fā)者們配置多數(shù)據(jù)源,據(jù)我目前所了解到的喷楣,一般分為兩種方式靜態(tài)與動(dòng)態(tài)(分包和AOP)趟大。本文使用的是動(dòng)態(tài)的方式。
  3. 因?yàn)槲覀兛刂剖嵌鄶?shù)據(jù)源DataSource铣焊,而并不用關(guān)心到底是用哪種方式去使用,比如源生JDBC罕伯、MyBatis曲伊、Hibernate等上層的使用方法都是一樣的。所以本文以本人常用的MyBatis-Plus操作數(shù)據(jù)源為例追他。
  4. 本文中數(shù)據(jù)庫用的是mysql5.7坟募。完整代碼地址在結(jié)尾!邑狸!
  5. 動(dòng)態(tài)方式懈糯,就是使用AbstractRoutingDataSource的實(shí)現(xiàn)類通過AOP或者手動(dòng)處理實(shí)現(xiàn)動(dòng)態(tài)的使用我們的數(shù)據(jù)源,這樣的入侵性較低单雾,非常好的滿足使用的需求赚哗。具體選擇哪個(gè)數(shù)據(jù)源是由determineCurrentLookupKey()方法的返回值決定的,該方法需要我們繼承AbstractRoutingDataSource來重寫够掠。

第一步,分別創(chuàng)建兩個(gè)數(shù)據(jù)庫db1哭廉,db2钝荡,sql如下

db1

CREATE DATABASE db1;

use db1;

CREATE TABLE user
(
    id INT NOT NULL COMMENT '主鍵ID',
    name VARCHAR(50) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年齡',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
    PRIMARY KEY (id)
)CHARSET=utf8 ENGINE=InnoDB COMMENT '用戶表';

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

select * from user;

db2

CREATE DATABASE db2;

use db2;

CREATE TABLE task
(
    id INT NOT NULL COMMENT '主鍵ID',
    name VARCHAR(50) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年齡',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
    PRIMARY KEY (id)
)CHARSET=utf8 ENGINE=InnoDB COMMENT '任務(wù)表';

INSERT INTO task (id, name, age, email) VALUES
(1, '李白', 18, 'test1@baomidou.com'),
(2, '露娜', 20, 'test2@baomidou.com'),
(3, '韓信', 28, 'test3@baomidou.com'),
(4, '橘右京', 21, 'test4@baomidou.com'),
(5, '百里玄刺', 24, 'test5@baomidou.com');

select * from task;

第二步,在pom.xml加入依賴梁剔,如下

<dependency>
   <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.1</version>
</dependency>
<!-- MySQL驅(qū)動(dòng) -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

第三步荣病,在application.yml配置多數(shù)據(jù)源,mybatis-plus相關(guān)配置

spring:
  # 配置數(shù)據(jù)源
  datasource:
    db1:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://xxx:3306/db1?useUnicode=true&characterEncoding=utf-8
      username: xxx
      password: xxx
    db2:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://xxx:3306/db2?useUnicode=true&characterEncoding=utf-8
      username: xxx
      password: xxx
  application:
    name: dynamicmultipledatasources-demo-server

# mybatis-plus相關(guān)配置
mybatis-plus:
  configuration:
    #不開啟二級(jí)緩存
    cache-enabled: false
    # 是否開啟自動(dòng)駝峰命名規(guī)則映射:從數(shù)據(jù)庫列名到Java屬性駝峰命名的類似映射
    map-underscore-to-camel-case: true
    # 如果查詢結(jié)果中包含空值的列,則 MyBatis 在映射的時(shí)候终惑,不會(huì)映射這個(gè)字段
    call-setters-on-nulls: true
    # 這個(gè)配置會(huì)將執(zhí)行的sql打印出來悯嗓,在開發(fā)或測(cè)試的時(shí)候可以用
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

server:
  port: 8189

第四步,將spring boot自帶的DataSourceAutoConfiguration禁掉涡扼,因?yàn)樗鼤?huì)讀取application.yml文件的spring.datasource.*屬性并自動(dòng)配置單數(shù)據(jù)源吃沪。在@SpringBootApplication注解中添加exclude屬性即可,如下

DynamicMultipleDataSourcesApplication

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DynamicMultipleDataSourcesApplication {

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

}

第五步票彪,創(chuàng)建文件DynamicDataSource不狮,DynamicDataSourceConfig,DataSourceContextHolder摇零,MybatisPlusConfig推掸,DataSourceType谅畅,如下

DynamicDataSource粘优,用于繼承AbstractRoutingDataSource來重寫determineCurrentLookupKey()方法

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }

}

DynamicDataSourceConfig敬飒,用于配置數(shù)據(jù)源

import com.luoyu.dynamicmultipledatasources.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
//basePackages 我們接口文件的地址
@MapperScan(basePackages = "com.luoyu.dynamicmultipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory")
public class DynamicDataSourceConfig {

    // 將這個(gè)對(duì)象放入Spring容器中
    @Bean("db1DataSource")
    // 表示這個(gè)數(shù)據(jù)源是默認(rèn)數(shù)據(jù)源
    @Primary
    // 讀取配置參數(shù)映射成為一個(gè)對(duì)象
    @ConfigurationProperties(prefix = "spring.datasource.db1", ignoreUnknownFields = false)
    public DataSource getDB1DateSource() {
        return DataSourceBuilder.create().build();
    }


    @Bean("db2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db2", ignoreUnknownFields = false)
    public DataSource getDB2DateSource() {
        return DataSourceBuilder.create().build();
    }


    @Bean("dynamicDataSource")
    public DynamicDataSource dynamicDataSource(@Qualifier("db1DataSource") DataSource db1DataSource,
                                        @Qualifier("db2DataSource") DataSource db2DataSource) {
        // 這個(gè)地方是比較核心的targetDataSource集合是我們數(shù)據(jù)庫和名字之間的映射
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.DB1, db1DataSource);
        targetDataSource.put(DataSourceType.DB2, db2DataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        // 設(shè)置默認(rèn)對(duì)象
        dataSource.setDefaultTargetDataSource(db1DataSource);
        return dataSource;
    }

    @Bean("transactionManager")
    public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }

    @Bean("SqlSessionFactory")
    public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource, @Qualifier("mybatisplusConfiguration") org.apache.ibatis.session.Configuration configuration)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        // 使mybatis配置生效无拗,加載順序問題
        bean.setConfiguration(configuration);
        bean.setMapperLocations(
                // 設(shè)置我們的xml文件路徑
                new PathMatchingResourcePatternResolver().getResources("classpath*:com/luoyu/dynamicmultipledatasources/mapper/xml/*.xml"));
        bean.setTypeAliasesPackage("com.luoyu.dynamicmultipledatasources.entity");
        return bean.getObject();
    }

}

DataSourceContextHolder,用于綁定當(dāng)前線程的數(shù)據(jù)源

import com.luoyu.dynamicmultipledatasources.enums.DataSourceType;

public class DataSourceContextHolder {

    // 使用ThreadLocal保證線程安全
    private static final ThreadLocal<DataSourceType> TYPE = new ThreadLocal<>();

    /**
     * 往當(dāng)前線程里設(shè)置數(shù)據(jù)源
     */
    public static void setDataSourceType(DataSourceType dataSourceType) {
        if (dataSourceType == null) {
            throw new NullPointerException();
        }
        TYPE.set(dataSourceType);
    }

    /**
     * 獲取數(shù)據(jù)源
     */
    public static DataSourceType getDataSourceType() {
        DataSourceType dataSourceType = TYPE.get() == null ? DataSourceType.DB1 : TYPE.get();
        return dataSourceType;
    }

    /**
     * 清空數(shù)據(jù)源
     */
    public static void clearDataSourceType() {
        TYPE.remove();
    }

}

MybatisPlusConfig

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.Order;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @Description: MybatisPlus配置類
 * @Author: jinhaoxun
 * @Date: 2020/2/13 上午11:34
 * @Version: 1.0.0
 */
@EnableTransactionManagement
@Configuration
@Order(-1)
public class MybatisPlusConfig {

    /**
     * 使application.properties配置生效昧碉,如果不主動(dòng)配置英染,由于@Order配置順序不同,將導(dǎo)致配置不能及時(shí)生效被饿,多數(shù)據(jù)源配置駝峰法生效
     * @return 數(shù)據(jù)源
     */
    @Bean("mybatisplusConfiguration")
    @ConfigurationProperties(prefix = "mybatis-plus.configuration")
    @Scope("prototype")
    public org.apache.ibatis.session.Configuration globalConfiguration() {
        return new org.apache.ibatis.session.Configuration();
    }

    /**
     * mybatis-plus SQL執(zhí)行效率插件【生產(chǎn)環(huán)境可以關(guān)閉】四康,設(shè)置 dev test 環(huán)境開啟
     */
//    @Bean
//    @Profile({"dev", "qa"})
//    public PerformanceInterceptor performanceInterceptor() {
//        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//        performanceInterceptor.setMaxTime(1000);
//        performanceInterceptor.setFormat(true);
//        return performanceInterceptor;
//    }

    /**
     * 分頁插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 設(shè)置請(qǐng)求的頁面大于最大頁后操作, true調(diào)回到首頁狭握,false 繼續(xù)請(qǐng)求  默認(rèn)false
        paginationInterceptor.setOverflow(false);
        // 設(shè)置最大單頁限制數(shù)量闪金,默認(rèn) 500 條,-1 不受限制
        paginationInterceptor.setLimit(500);
        // 開啟 count 的 join 優(yōu)化,只針對(duì)部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }

}

DataSourceType,枚舉類哎垦,用于選擇特定的數(shù)據(jù)源

/**
 * 枚舉類囱嫩,用于選擇特定的數(shù)據(jù)源
 */
public enum DataSourceType {

    DB1, DB2

}

第六步,創(chuàng)建Task漏设,User墨闲,如下

Task

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * <p>
 * 
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class Task implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主鍵id
     */
    private Integer id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    /**
     * 郵箱
     */
    private String email;

}

User

import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主鍵id
     */
    private Integer id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 年齡
     */
    private Integer age;

    /**
     * 郵箱
     */
    private String email;

}

第七步,分別創(chuàng)建UserMapper郑口,UserMapper.xml鸳碧,TaskMapper,TaskMapper.xml犬性,如下

UserMapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luoyu.staticmultipledatasources.entity.User;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
public interface UserMapper extends BaseMapper<User> {

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    User selectById(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    int updateName(@Param("id") int id, @Param("name") String name);

}

UserMapper.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.luoyu.dynamicmultipledatasources.mapper.UserMapper">

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.luoyu.dynamicmultipledatasources.entity.User">
        select * from user where id = #{id};
    </select>

    <update id="updateName">
        update user set name = #{name} where id = #{id};
    </update>

</mapper>

TaskMapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.luoyu.staticmultipledatasources.entity.Task;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Mapper
public interface TaskMapper extends BaseMapper<Task> {

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    Task selectById(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    int updateName(@Param("id") int id, @Param("name") String name);

}

TaskMapper.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.luoyu.dynamicmultipledatasources.mapper.TaskMapper">

    <select id="selectById" parameterType="java.lang.Integer" resultType="com.luoyu.dynamicmultipledatasources.entity.Task">
        select * from task where id = #{id};
    </select>

    <update id="updateName">
        update task set name = #{name} where id = #{id};
    </update>
    
</mapper>

第八步瞻离,創(chuàng)建自定義注解,AOP切面ChangeDataSource仔夺,ChangeDataSourceAspect琐脏,如下

ChangeDataSource

import java.lang.annotation.*;

/**
 * 切換數(shù)據(jù)注解 可以用于類或者方法級(jí)別 方法級(jí)別優(yōu)先級(jí) > 類級(jí)別
 */
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChangeDataSource {

    //該值即key值,默認(rèn)使用默認(rèn)數(shù)據(jù)庫
    String value() default "db1";

}

ChangeDataSourceAspect

import com.luoyu.dynamicmultipledatasources.config.DataSourceContextHolder;
import com.luoyu.dynamicmultipledatasources.enums.DataSourceType;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(-1)
public class ChangeDataSourceAspect {

    /**
     * 切換數(shù)據(jù)源
     */
    @Before("@annotation(changeDataSource)")
    public void changeDataSourceType(JoinPoint point, ChangeDataSource changeDataSource) throws Throwable {
        String value = changeDataSource.value();
        if (value.equals("db1")){
            DataSourceContextHolder.setDataSourceType(DataSourceType.DB1);
        }else if (value.equals("db2")){
            DataSourceContextHolder.setDataSourceType(DataSourceType.DB2);
        }else {
            // 默認(rèn)使用db1
            DataSourceContextHolder.setDataSourceType(DataSourceType.DB1);
        }

    }

    /**
     * 清除數(shù)據(jù)源
     */
    @After("@annotation(changeDataSource)")
    public void clearDataSourceType(JoinPoint point, ChangeDataSource changeDataSource) {
        DataSourceContextHolder.clearDataSourceType();
    }

}

第九步缸兔,創(chuàng)建IUserService日裙,UserServiceImpl,ITaskService惰蜜,TaskServiceImpl昂拂,為了實(shí)現(xiàn)更優(yōu)雅的動(dòng)態(tài)數(shù)據(jù)源的切換,使用AOP+自定義注解的方式實(shí)現(xiàn)對(duì)方法級(jí)別的數(shù)據(jù)源切換抛猖。因?yàn)樽⒔庾畹椭荒芏x在方法上(而非代碼塊上)格侯,所以此種方式最細(xì)粒度為方法級(jí)別,99.99%情況下都?jí)蛴昧瞬浦缦?/h2>

使用方法只需要在Service類的方法加上@ChangeDataSource("db2")注解联四,指定db2數(shù)據(jù)源即可,沒有添加的話撑教,默認(rèn)db1數(shù)據(jù)源

IUserService

import com.baomidou.mybatisplus.extension.service.IService;
import com.luoyu.dynamicmultipledatasources.entity.User;

/**
 * <p>
 *  服務(wù)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
public interface IUserService extends IService<User> {

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    User selectById(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    int updateName(int id, String name);

}

UserServiceImpl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luoyu.dynamicmultipledatasources.entity.User;
import com.luoyu.dynamicmultipledatasources.mapper.UserMapper;
import com.luoyu.dynamicmultipledatasources.service.IUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Resource
    private UserMapper userMapper;

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    @Override
    public User selectById(Integer id) {
        return userMapper.selectById(id);
    }

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateName(int id, String name) {
        int count = userMapper.updateName(id, name);
        // 此處報(bào)錯(cuò)事務(wù)回滾
        int i = 1/0;
        return count;
    }

}

ITaskService

import com.baomidou.mybatisplus.extension.service.IService;
import com.luoyu.dynamicmultipledatasources.entity.Task;

/**
 * <p>
 *  服務(wù)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
public interface ITaskService extends IService<Task> {

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    Task selectById(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    int updateName(int id, String name);

}

TaskServiceImpl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luoyu.dynamicmultipledatasources.aop.ChangeDataSource;
import com.luoyu.dynamicmultipledatasources.entity.Task;
import com.luoyu.dynamicmultipledatasources.mapper.TaskMapper;
import com.luoyu.dynamicmultipledatasources.service.ITaskService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements ITaskService {

    @Resource
    private TaskMapper taskMapper;

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    @ChangeDataSource("db2")
    @Override
    public Task selectById(Integer id) {
        return taskMapper.selectById(id);
    }

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    @Transactional(rollbackFor = Exception.class)
    @ChangeDataSource("db2")
    @Override
    public int updateName(int id, String name) {
        int count = taskMapper.updateName(id, name);
        int i = 1/0;
        return count;
    }
    
}

第十步朝墩,創(chuàng)建TaskController,UserController伟姐,如下

TaskController

import com.luoyu.dynamicmultipledatasources.entity.Task;
import com.luoyu.dynamicmultipledatasources.service.ITaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@RestController
public class TaskController {

    @Autowired
    private ITaskService iTaskService;

    /**
     * 查詢db2
     */
    @GetMapping(value = "/task")
    public Task getTaskById(@RequestParam("id") Integer id) {
        return iTaskService.selectById(id);
    }

    /**
     * 測(cè)試事物
     */
    @PutMapping(value = "/task")
    public int updateName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
        return iTaskService.updateName(id, name);

    }

}

UserController

import com.luoyu.dynamicmultipledatasources.entity.User;
import com.luoyu.dynamicmultipledatasources.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author luoyu
 * @since 2020-02-13
 */
@RestController
public class UserController {

    @Autowired
    private IUserService iUserService;

    /**
     * 查詢db1
     */
    @GetMapping(value = "/user")
    public User getUserById(@RequestParam("id") Integer id) {
        return iUserService.selectById(id);
    }

    /**
     * 測(cè)試事物
     */
    @PutMapping(value = "/user")
    public int updateName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
        return iUserService.updateName(id, name);

    }

}

第十一步收苏,啟動(dòng)項(xiàng)目,使用postman進(jìn)行測(cè)試愤兵,如下

測(cè)試查db1鹿霸,使用GET請(qǐng)求http://localhost:8189/user?id=2

image.png

測(cè)試db1事務(wù),修改報(bào)錯(cuò)后再查db1秆乳,發(fā)現(xiàn)事務(wù)回滾了懦鼠,使用PUT請(qǐng)求http://localhost:8189/user?id=2&name=test999

image.png

測(cè)試查db2,使用GET請(qǐng)求http://localhost:8189/task?id=2

image.png

測(cè)試db2事務(wù),修改報(bào)錯(cuò)后再查db2葛闷,發(fā)現(xiàn)事務(wù)回滾了憋槐,使用PUT請(qǐng)求http://localhost:8189/task?id=2&name=test999

image.png

第十二步,有時(shí)候需要內(nèi)部方法調(diào)用淑趾,這個(gè)時(shí)候直接調(diào)用的話AOP是會(huì)失效的阳仔,如下

說明

  1. 這個(gè)地方跟spring的@Transactional事務(wù)失效原因一致,因?yàn)槎际腔贏OP動(dòng)態(tài)代理的模式扣泊,所以都可以用以下方法解決問題近范。
  2. 原因在于進(jìn)行內(nèi)部方法調(diào)用的時(shí)候沒用到代理類,而是直接使用當(dāng)前實(shí)例去進(jìn)行自身調(diào)用延蟹,所以不會(huì)觸發(fā)AOP切面。
  3. 解決思路很簡單阱飘,只要我們?cè)谶M(jìn)行內(nèi)部方法調(diào)用的時(shí)候斥杜,手動(dòng)去spring容器拿到當(dāng)前類的代理類進(jìn)行調(diào)用,這個(gè)時(shí)候就可以觸發(fā)AOP切面類沥匈。步驟如下

第一步蔗喂,創(chuàng)建SpringUtils工具類,如下

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

@Component
public class SpringUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> cla) {
        return applicationContext.getBean(cla);
    }

    public static <T> T getBean(String name, Class<T> cal) {
        return applicationContext.getBean(name, cal);
    }

    public static Object getBean(String name){
        return applicationContext.getBean(name);
    }

    public static String getProperty(String key) {
        return applicationContext.getBean(Environment.class).getProperty(key);
    }

}

第二步高帖,在TaskController缰儿,ITaskService,TaskServiceImpl類中新增測(cè)試方法散址,如下

TaskController

import com.luoyu.dynamicmultipledatasources.entity.Task;
import com.luoyu.dynamicmultipledatasources.service.ITaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@RestController
public class TaskController {

    @Autowired
    private ITaskService iTaskService;

    /**
     * 查詢db2
     */
    @GetMapping(value = "/task")
    public Task getTaskById(@RequestParam("id") Integer id) {
        return iTaskService.selectById(id);
    }

    /**
     * 測(cè)試事物
     */
    @PutMapping(value = "/task")
    public int updateName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
        return iTaskService.updateName(id, name);

    }

    /**
     * 測(cè)試解決內(nèi)部方法調(diào)用AOP失效問題
     */
    @GetMapping(value = "/taskByInside")
    public Task getTaskByInside(@RequestParam("id") Integer id) {
        return iTaskService.selectByInside(id);
    }

}

ITaskService

import com.baomidou.mybatisplus.extension.service.IService;
import com.luoyu.dynamicmultipledatasources.entity.Task;

/**
 * <p>
 *  服務(wù)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
public interface ITaskService extends IService<Task> {

    /**
     * @Author: jinhaoxun
     * @Description: 解決內(nèi)部方法調(diào)用AOP失效問題
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    Task selectByInside(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    Task selectById(Integer id);

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    int updateName(int id, String name);

}

TaskServiceImpl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luoyu.dynamicmultipledatasources.aop.ChangeDataSource;
import com.luoyu.dynamicmultipledatasources.entity.Task;
import com.luoyu.dynamicmultipledatasources.mapper.TaskMapper;
import com.luoyu.dynamicmultipledatasources.service.ITaskService;
import com.luoyu.dynamicmultipledatasources.util.SpringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * <p>
 *  服務(wù)實(shí)現(xiàn)類
 * </p>
 *
 * @author jinhaoxun
 * @since 2020-02-13
 */
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements ITaskService {

    @Resource
    private TaskMapper taskMapper;

    /**
     * @Author: jinhaoxun
     * @Description: 解決內(nèi)部方法調(diào)用AOP失效問題
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    @Override
    public Task selectByInside(Integer id) {
        return this.getProxy().selectById(id);
    }

    /**
     * @Author: jinhaoxun
     * @Description: 從spring容器里手動(dòng)拿到AOP代理類乖阵,解決AOP失效問題
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    private TaskServiceImpl getProxy(){
        return SpringUtils.getBean(this.getClass());
    }

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @Date: 2020/2/13 下午12:06
     * @Throws:
     */
    @ChangeDataSource("db2")
    @Override
    public Task selectById(Integer id) {
        return taskMapper.selectById(id);
    }

    /**
     * @Author: jinhaoxun
     * @Description:
     * @param id id
     * @param name 姓名
     * @Date: 2020/2/13 下午12:06
     * @Return: int
     * @Throws:
     */
    @Transactional(rollbackFor = Exception.class)
    @ChangeDataSource("db2")
    @Override
    public int updateName(int id, String name) {
        int count = taskMapper.updateName(id, name);
        int i = 1/0;
        return count;
    }

}

第三步,測(cè)試解決內(nèi)部方法調(diào)用AOP失效問題预麸,使用GET請(qǐng)求http://localhost:8189/taskByInside?id=2

image.png

完整代碼地址:https://github.com/Jinhx128/springboot-demo

注:此工程包含多個(gè)module瞪浸,本文所用代碼均在dynamicmultipledatasources-demo模塊下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吏祸,隨后出現(xiàn)的幾起案子默终,更是在濱河造成了極大的恐慌钓觉,老刑警劉巖妇智,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婶熬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡床估,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門诱渤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丐巫,“玉大人,你說我怎么就攤上這事〉蓦剩” “怎么了碑韵?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缎脾。 經(jīng)常有香客問我祝闻,道長,這世上最難降的妖魔是什么遗菠? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任联喘,我火速辦了婚禮,結(jié)果婚禮上辙纬,老公的妹妹穿的比我還像新娘豁遭。我一直安慰自己,他們只是感情好贺拣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布蓖谢。 她就那樣靜靜地躺著,像睡著了一般譬涡。 火紅的嫁衣襯著肌膚如雪闪幽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天昂儒,我揣著相機(jī)與錄音沟使,去河邊找鬼。 笑死渊跋,一個(gè)胖子當(dāng)著我的面吹牛腊嗡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拾酝,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼燕少,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蒿囤?” 一聲冷哼從身側(cè)響起客们,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎材诽,沒想到半個(gè)月后底挫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脸侥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年建邓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睁枕。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡官边,死狀恐怖沸手,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情注簿,我是刑警寧澤契吉,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站诡渴,受9級(jí)特大地震影響捐晶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玩徊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一租悄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恩袱,春花似錦泣棋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至澈吨,卻和暖如春把敢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谅辣。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工修赞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桑阶。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓柏副,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚣录。 傳聞我的和親對(duì)象是個(gè)殘疾皇子割择,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容