Springboot+mybatis+mysql+注解+AOP配置多源数据库
整个项目代码在Q群:903233760
1.创建springboot项目
2.向pom.xml导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--<version>2.5.4</version>-->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<version>8.0.26</version>-->
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<!--<version>2.5.4</version>-->
</dependency>
3.创建数据库表
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test1` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `test1`;
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`name` varchar(64) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `user` */
insert into `user`(`name`) values ('李四');
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test2` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `test2`;
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`name` varchar(40) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `user` */
insert into `user`(`name`) values ('张三');
4.在spring boot配置文件(application.yml)中配置连接的数据库信息
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
master:
url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC
username: root
password: root
slave:
enabled: true
url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC
username: root
password: root
5.创建DruidConfig配置类
在配置类中配置多个Datasource
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
这个注解的意思是当spring.datasource.druid.slave.enabled==true时,才创建相应的DataSource加入到spring容器
将刚才创建的多个DataSource存放在一起方便以后的切换和注入这里默认masterDataSource为主数据源,将主数据源和所有数据源传给DynamicDataSource类
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidConfig {
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(){
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource(){
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
@Bean(name="dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource,DataSource slaveDataSource){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(),masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(),slaveDataSource);
return new DynamicDataSource(masterDataSource,targetDataSources);
}
}
6.创建DynamicDataSource继承AbstractRoutingDataSource类
DynamicDataSource类继承了AbstractRoutingDataSource类实现动态数据源切换
在DynamicDataSource构造方法中调用了父类setDefaultTargetDataSource方法把主数据源中的信息赋值给父类的defaultTargetDataSource属性
调用了父类setTargetDataSources方法把所有数据源中的信息赋值给父类的targetDataSources属性
最后调用了父类afterPropertiesSet方法。在父类的afterPropertiesSet方法中,可以观察到他将我们所设置给父的所有数据源信息,转移到父类的resolvedDataSources属性中,并将我们传给父类的主数据源信息递给父类的resolvedDefaultDataSource属性
DynamicDataSource类还实现了父类的(AbstractRoutingDataSource类)determineCurrentLookupKey方法 这个方法返回值是我们决定切换的数据源是哪个。
在父类中determineTargetDataSource方法调用了我们在子类中实现的determineCurrentLookupKey方法,获取到我们索要切换的数据库类型,在从所有数据源中获取到DataSource并返回,如果我们切换的数据源类型没有就返回我们的默认的主数据源。
因为在我们查数据库的时候每次都要获取数据库连接都会调用这个类的determineTargetDataSource方法这个方法决定了我们的数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource , Map<Object,Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override //决定现在的
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
7.实现DynamicDataSourceContextHolder类
实现了一个DynamicDataSourceContextHolder用来存放我们要切换数据源的类型,这里有注解不多说了
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType)
{
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType()
{
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
{
CONTEXT_HOLDER.remove();
}
}
8.实现DataSourceType数据库类型枚举类
public enum DataSourceType {
MASTER,
SLAVE
}
9.创建User、UserMapper、UserMapper.xml类
public class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserMapper {
public List<User> selectList2();
}
<?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.UserMapper">
<select id="selectList2" resultType="com.example.User">
select * from user
</select>
</mapper>
10.在pom文件中指定Mapper文件资源的位置
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
11.创建DataSource注解类
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}
12.创建DataSourceAspect,对使用注解的方法做切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* 多数据源处理
*
* @author ruoyi
*/
@Aspect
@Component
public class DataSourceAspect
{
@Pointcut("@annotation(com.example.DataSource)"
+ "|| @within(com.example.DataSource)")
public void dsPointCut()
{
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable
{
DataSource dataSource = getDataSource(point);
if (dataSource!=null)
{
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try
{
return point.proceed();
}
finally
{
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point)
{
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource))
{
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
13.创建UserService类,从两个不同的数据源中查询信息
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@DataSource(value=DataSourceType.SLAVE)
public List<User> selectAllUser(){
return userMapper.selectList2();
}
@DataSource(value=DataSourceType.MASTER)
public List<User> selectAllUser2(){
return userMapper.selectList2();
}
}
14.创建Controller类,将查询的信息输出到控制台
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@Autowired
UserService userService;
@GetMapping("/test")
public void test(){
System.out.println("数据源1查询的数据:"+userService.selectAllUser());
System.out.println("数据源2查询的数据:"+userService.selectAllUser2());
}
}
15.启动springboot应用 打开浏览器访问http://localhost:8080/test会看到控制台输出一下信息,说明成功了
16.项目结构
17.总结跟着Ruoyi大佬学到了很多东西,对于刚走上开发的我来说启发很大