项目结构
情景描述
异源数据,在一个工程中处理后,传入不同数据库中保存。
类似下图所想完成(只会windows自带画图)
准备两个测试数据库
上号
pom.xml
不耍流氓,贴pom。
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.multiple</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1. springboot启动类
package com.multiple.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
//禁止其自动加载配置文件
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
@MapperScan("com.multiple.demo.dao")
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
2. Controller层
此处可以更改为对应业务逻辑处理
package com.multiple.demo.Collector;
import com.multiple.demo.Common.ChangeAnnotation;
import com.multiple.demo.Service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@Autowired
private UserServiceImpl userServiceImpl;
@RequestMapping("/getRequestDBObj")
public void getRequestDBObj() {
//假使获取到数据,根据其中一个标记字段确定其处理过后存储的数据库表名为test1
String table = "test1";
//在其aop之前修改注解的value
ChangeAnnotation.change(table);
String str1 = userServiceImpl.selectByPrimaryKey(2);
System.out.println(str1);
System.out.println();
table = "test2";
ChangeAnnotation.change(table);
String str2 = userServiceImpl.selectByPrimaryKey(2);
System.out.println(str2);
}
}
3. common包
- 自定义注解
package com.multiple.demo.common;
import java.lang.annotation.*;
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Retention(RetentionPolicy.RUNTIME)
//用于描述方法
@Target({ElementType.METHOD})
@Documented
//自定义注解DS 默认值readTestDb
public @interface DS {
String value() default "test1";
}
- 修改注解值
package com.multiple.demo.common;
import com.multiple.demo.Service.UserServiceImpl;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
public class ChangeAnnotation {
public static void change(String table){
String annotation = table;
try {
//获取UserServiceImpl类下所有方法
Method[]methods = UserServiceImpl.class.getMethods();
int i= 0;
//判断方法上是否有注解DS
while (methods[i].getAnnotation(DS.class)!=null){
DS ds = methods[i].getAnnotation(DS.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(ds);
Field value = invocationHandler.getClass().getDeclaredField("memberValues");
value.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
String val = (String) memberValues.get("value");
System.out.println("改变前:" + val);
val = annotation;
memberValues.put("value", val);
System.out.println("改变后:" + ds.value());
i++;
}
}catch (Exception e){
e.printStackTrace();
}
}
}
- 存储对应线程的数据源
package com.multiple.demo.common;
public class DataSourceContextHolder {
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "test1";
//线程死去 threa
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
System.out.println("切换到{"+dbType+"}数据源");
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
- 数据源类
package com.multiple.demo.common;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
//无法动态的增加数据源
@Override
protected Object determineCurrentLookupKey() {
System.out.println("数据源为" + DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
- 切面
package com.multiple.demo.common;
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
//
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(com.multiple.demo.common.DS)")
@SuppressWarnings("rawtypes")
public void beforeSwitchDS(JoinPoint point) {
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
//System.out.println(dataSource);
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(com.multiple.demo.common.DS)")
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDB();
}
}
数据库配置类
重要!!!
根据yam.yml获取数据库相关信息,生成数据库连接
package com.multiple.demo.config;
import com.multiple.demo.common.DynamicDataSource;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
//此类需要更改
@Configuration
public class DataSourceConfig {
@Bean(name = "test1")
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource test1Db() {
return DataSourceBuilder.create().build();
}
@Bean(name = "test2")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource test2Db() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
*
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(test1Db());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap<Object, Object>();
dsMap.put("test1", test1Db());
dsMap.put("test2", test2Db());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事物
*
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
// @Bean
// public SqlSessionFactory sqlSessionFactory() throws Exception {
// SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// sqlSessionFactoryBean.setDataSource(dynamicDataSource());
// //此处设置为了解决找不到mapper文件的问题
// sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
// return sqlSessionFactoryBean.getObject();
// }
//
// @Bean
// public SqlSessionTemplate sqlSessionTemplate() throws Exception {
// return new SqlSessionTemplate(sqlSessionFactory());
// }
}
Service层
- service接口
package com.multiple.demo.service;
import org.springframework.stereotype.Service;
@Service
public interface UserService {
String selectByPrimaryKey(Integer userId);
}
- 实现类
package com.multiple.demo.service;
import com.multiple.demo.common.DS;
import com.multiple.demo.dao.UserMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
@DS
public String selectByPrimaryKey(Integer userId) {
return userMapper.selectByPrimaryKey(userId);
}
}
model
user对象
package com.multiple.demo.model;
public class User {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name == null ? null : name.trim();
}
}
DAO层
接口
package com.multiple.demo.dao;
public interface UserMapper {
String selectByPrimaryKey(Integer id);
}
mapper映射
<?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.multiple.demo.dao.UserMapper" >
<resultMap id="BaseResultMap" type="com.multiple.demo.model.User" >
<result column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
</resultMap>
<sql id="Example_Where_Clause" >
<where >
<foreach collection="oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause" >
<where >
<foreach collection="example.oredCriteria" item="criteria" separator="or" >
<if test="criteria.valid" >
<trim prefix="(" suffix=")" prefixOverrides="and" >
<foreach collection="criteria.criteria" item="criterion" >
<choose >
<when test="criterion.noValue" >
and ${criterion.condition}
</when>
<when test="criterion.singleValue" >
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue" >
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue" >
and ${criterion.condition}
<foreach collection="criterion.value" item="listItem" open="(" close=")" separator="," >
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List" >
id, name
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="java.lang.String">
SELECT name FROM user WHERE id = #{id,jdbcType=INTEGER}
</select>
</mapper>
application.yml
spring:
datasource:
test1:
jdbc-url: jdbc:mysql://172.8.8.8:3306/test1?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
test2:
jdbc-url: jdbc:mysql://172.8.8.8:3306/test2?useUnicode=true&characterEncoding=utf-8
username: root
password: root
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.multiple.demo.model
运行
google键入
注意
此案例只能是根据数据动态切换已知数据库连接,即数据库信息已在code中配好,没有做到根据数据动态生成数据库/表。
网上看了好多,没法用。
多线程操作会有问题,目前还在研究。
—————————————————————————————————
TODO
多线程操作,修改注解value会有资源共享问题。所以打算取数据特征值作为对应数据库参数存入Threadlocal,下面拿到threadlocal中的,切换数据源。