说明
- 随着应用用户数量的增加,相应的并发请求的数量也会跟着不断增加,慢慢地,单个数据库已经没有办法满足我们频繁的数据库操作请求了。
- 在某些场景下,我们可能会需要配置多个数据源,使用多个数据源(例如实现数据库的读写分离)来缓解系统的压力等,同样的,SpringBoot官方提供了相应的实现来帮助开发者们配置多数据源,据我目前所了解到的,一般分为两种方式静态与动态(分包和AOP)。本文使用的是动态的方式。
- 因为我们控制是多数据源DataSource,而并不用关心到底是用哪种方式去使用,比如源生JDBC、MyBatis、Hibernate等上层的使用方法都是一样的。所以本文以本人常用的MyBatis-Plus操作数据源为例。
- 本文中数据库用的是mysql5.7。完整代码地址在结尾!!
- 动态方式,就是使用AbstractRoutingDataSource的实现类通过AOP或者手动处理实现动态的使用我们的数据源,这样的入侵性较低,非常好的满足使用的需求。具体选择哪个数据源是由determineCurrentLookupKey()方法的返回值决定的,该方法需要我们继承AbstractRoutingDataSource来重写。
第一步,分别创建两个数据库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 '任务表';
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驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
第三步,在application.yml配置多数据源,mybatis-plus相关配置
spring:
# 配置数据源
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相关配置
mybatis-plus:
configuration:
#不开启二级缓存
cache-enabled: false
# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
map-underscore-to-camel-case: true
# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
call-setters-on-nulls: true
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 8189
第四步,将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.yml文件的spring.datasource.*属性并自动配置单数据源。在@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);
}
}
第五步,创建文件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,用于配置数据源
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 {
// 将这个对象放入Spring容器中
@Bean("db1DataSource")
// 表示这个数据源是默认数据源
@Primary
// 读取配置参数映射成为一个对象
@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) {
// 这个地方是比较核心的targetDataSource集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceType.DB1, db1DataSource);
targetDataSource.put(DataSourceType.DB2, db2DataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
// 设置默认对象
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(
// 设置我们的xml文件路径
new PathMatchingResourcePatternResolver().getResources("classpath*:com/luoyu/dynamicmultipledatasources/mapper/xml/*.xml"));
bean.setTypeAliasesPackage("com.luoyu.dynamicmultipledatasources.entity");
return bean.getObject();
}
}
DataSourceContextHolder,用于绑定当前线程的数据源
import com.luoyu.dynamicmultipledatasources.enums.DataSourceType;
public class DataSourceContextHolder {
// 使用ThreadLocal保证线程安全
private static final ThreadLocal<DataSourceType> TYPE = new ThreadLocal<>();
/**
* 往当前线程里设置数据源
*/
public static void setDataSourceType(DataSourceType dataSourceType) {
if (dataSourceType == null) {
throw new NullPointerException();
}
TYPE.set(dataSourceType);
}
/**
* 获取数据源
*/
public static DataSourceType getDataSourceType() {
DataSourceType dataSourceType = TYPE.get() == null ? DataSourceType.DB1 : TYPE.get();
return dataSourceType;
}
/**
* 清空数据源
*/
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配置生效,如果不主动配置,由于@Order配置顺序不同,将导致配置不能及时生效,多数据源配置驼峰法生效
* @return 数据源
*/
@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执行效率插件【生产环境可以关闭】,设置 dev test 环境开启
*/
// @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();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
DataSourceType,枚举类,用于选择特定的数据源
/**
* 枚举类,用于选择特定的数据源
*/
public enum DataSourceType {
DB1, DB2
}
第六步,创建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;
}
第七步,分别创建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>
第八步,创建自定义注解,AOP切面ChangeDataSource,ChangeDataSourceAspect,如下
ChangeDataSource
import java.lang.annotation.*;
/**
* 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChangeDataSource {
//该值即key值,默认使用默认数据库
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 {
/**
* 切换数据源
*/
@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 {
// 默认使用db1
DataSourceContextHolder.setDataSourceType(DataSourceType.DB1);
}
}
/**
* 清除数据源
*/
@After("@annotation(changeDataSource)")
public void clearDataSourceType(JoinPoint point, ChangeDataSource changeDataSource) {
DataSourceContextHolder.clearDataSourceType();
}
}
第九步,创建IUserService,UserServiceImpl,ITaskService,TaskServiceImpl,为了实现更优雅的动态数据源的切换,使用AOP+自定义注解的方式实现对方法级别的数据源切换。因为注解最低只能定义在方法上(而非代码块上),所以此种方式最细粒度为方法级别,99.99%情况下都够用了,如下
使用方法只需要在Service类的方法加上@ChangeDataSource("db2")注解,指定db2数据源即可,没有添加的话,默认db1数据源
IUserService
import com.baomidou.mybatisplus.extension.service.IService;
import com.luoyu.dynamicmultipledatasources.entity.User;
/**
* <p>
* 服务类
* </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>
* 服务实现类
* </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);
// 此处报错事务回滚
int i = 1/0;
return count;
}
}
ITaskService
import com.baomidou.mybatisplus.extension.service.IService;
import com.luoyu.dynamicmultipledatasources.entity.Task;
/**
* <p>
* 服务类
* </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>
* 服务实现类
* </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;
}
}
第十步,创建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);
}
/**
* 测试事物
*/
@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);
}
/**
* 测试事物
*/
@PutMapping(value = "/user")
public int updateName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
return iUserService.updateName(id, name);
}
}
第十一步,启动项目,使用postman进行测试,如下
第十二步,有时候需要内部方法调用,这个时候直接调用的话AOP是会失效的,如下
说明
- 这个地方跟spring的@Transactional事务失效原因一致,因为都是基于AOP动态代理的模式,所以都可以用以下方法解决问题。
- 原因在于进行内部方法调用的时候没用到代理类,而是直接使用当前实例去进行自身调用,所以不会触发AOP切面。
- 解决思路很简单,只要我们在进行内部方法调用的时候,手动去spring容器拿到当前类的代理类进行调用,这个时候就可以触发AOP切面类。步骤如下
第一步,创建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类中新增测试方法,如下
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);
}
/**
* 测试事物
*/
@PutMapping(value = "/task")
public int updateName(@RequestParam("id") Integer id, @RequestParam("name") String name) {
return iTaskService.updateName(id, name);
}
/**
* 测试解决内部方法调用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>
* 服务类
* </p>
*
* @author jinhaoxun
* @since 2020-02-13
*/
public interface ITaskService extends IService<Task> {
/**
* @Author: jinhaoxun
* @Description: 解决内部方法调用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>
* 服务实现类
* </p>
*
* @author jinhaoxun
* @since 2020-02-13
*/
@Service
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements ITaskService {
@Resource
private TaskMapper taskMapper;
/**
* @Author: jinhaoxun
* @Description: 解决内部方法调用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容器里手动拿到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;
}
}
注:此工程包含多个module,本文所用代码均在dynamicmultipledatasources-demo模块下