test:(多数据源写入业务代码中)

dev
zhang xu 2024-06-05 22:30:53 +08:00
parent cf7f3f068a
commit ae0974554a
16 changed files with 701 additions and 5 deletions

View File

@ -0,0 +1,45 @@
package com.muyu.domain.datasources;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
*
* @ClassName DataSource
* @Author
* @Date 2024/6/3 20:42
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class Datasource {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* ip
*/
private String ip;
/**
*
*/
@TableField("`database`")
private String database;
/**
*
*/
private String username;
/**
*
*/
private String password;
}

View File

@ -0,0 +1,41 @@
package com.muyu.domain.datasources;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName Result
* @Description
* @Author
* @Date 2024/3/13 11:34
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success(){
return new Result<>(200,"操作成功",null);
}
public static <T> Result<T> success(T data){
return new Result<>(200,"操作成功",data);
}
public static <T> Result<T> success(T data,String msg){
return new Result<>(200,msg,data);
}
public static <T> Result<T> error(){
return new Result<>(500,"操作失败",null);
}
public static <T> Result<T> error(String msg){
return new Result<>(500,msg,null);
}
}

View File

@ -0,0 +1,50 @@
package com.muyu.domain.datasources;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
/**
* song_info
*
* @author
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("song_info")
public class SongInfo implements Serializable {
private static final long serialVersionUID = 1L;
/** 编号 */
@TableId(value = "id",type = IdType.AUTO)
private Long id;
/** 歌名 */
private String name;
/** 类型 */
private String type;
/** 艺术家 */
private String artist;
/** 封面 */
private String cover;
/** 歌词 */
private String lrc;
/** 源文件 */
private String url;
}

View File

@ -0,0 +1,113 @@
package com.muyu.networking.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import lombok.extern.log4j.Log4j2;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* Druid
* @ClassName DruidDBConfig
* @Author
* @Date 2024/4/28 17:16
*/
@Log4j2
@Configuration
public class DruidDBConfig {
@Value("${spring.datasource.dynamic.datasource.slave.url}")
private String dbUrl;
@Value("${spring.datasource.dynamic.datasource.slave.username}")
private String username;
@Value("${spring.datasource.dynamic.datasource.slave.password}")
private String password;
@Value("${spring.datasource.dynamic.datasource.slave.driver-class-name}")
private String driverClassName;
// 连接池连接信息
@Value("${spring.datasource.dynamic.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.dynamic.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.dynamic.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.dynamic.druid.max-wait}")
private int maxWait;
@Bean
@Primary
@Qualifier("mainDataSource")
public DataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
// 基础连接信息
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
// 连接池连接信息
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
//是否缓存preparedStatement也就是PSCache。PSCache对支持游标的数据库性能提升巨大比如说oracle。在mysql下建议关闭。
datasource.setPoolPreparedStatements(false);
datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
//申请连接时执行validationQuery检测连接是否有效这里建议配置为TRUE防止取到的连接不可用
datasource.setTestOnBorrow(true);
//建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
datasource.setTestWhileIdle(true);
//用来检测连接是否有效的sql
datasource.setValidationQuery("select 1 from dual");
//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
datasource.setTimeBetweenEvictionRunsMillis(60000);
//配置一个连接在池中最小生存的时间单位是毫秒这里配置为3分钟180000
datasource.setMinEvictableIdleTimeMillis(180000);
datasource.setKeepAlive(true);
return datasource;
}
@Bean(name = "dynamicDataSource")
@Qualifier("dynamicDataSource")
public DynamicRoutingDataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource();
dynamicDataSource.setDebug(false);
//配置缺省的数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource());
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
//额外数据源配置 TargetDataSources
targetDataSources.put("mainDataSource", dataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
//用mybatis的这里会有点区别mybatis用的是SqlSessionFactoryBean
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//配置resources包下的xml文件扫描路径
//sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
/**
*
* @param dataSource
* @return org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
@Bean
public DataSourceTransactionManager transactionManager(DynamicRoutingDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

View File

@ -0,0 +1,158 @@
package com.muyu.networking.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import com.muyu.domain.datasources.Datasource;
import com.muyu.networking.context.DataSourceContextHolder;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* @ClassName
* @Author
* @Date 2024/4/28 17:01
*/
@Data
@Log4j2
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private boolean debug = true;
/**
*
*/
private volatile Map<Object, Object> customDataSources;
@Override
protected Object determineCurrentLookupKey() {
Long datasourceId = DataSourceContextHolder.getDataSource(); // 获取当前数据源
if(!Objects.isNull(datasourceId)){
Map<Object, Object> map = this.customDataSources; // 存储我们注册的数据源
if(map.containsKey(datasourceId)){
log.info("当前数据源是:{}",datasourceId);
}else{
log.info("不存在数据源:{}",datasourceId);
return null;
}
}else{
log.info("当前是默认数据源");
}
return datasourceId;
}
@Override
public void setTargetDataSources(Map<Object, Object> param) {
super.setTargetDataSources(param);
this.customDataSources = param; // 存储我们注册的数据源
}
/**
*
* @param dataSource
*/
public void checkCreateDataSource(Datasource dataSource){ // 数据源对象
Long datasourceId = dataSource.getId(); // 编号
Map<Object, Object> map = this.customDataSources; // 存储我们注册的数据源
if(map.containsKey(datasourceId)){
//这里检查一下之前创建的数据源,现在是否连接正常
DruidDataSource druidDataSource = (DruidDataSource) map.get(datasourceId);
boolean flag = true;
DruidPooledConnection connection = null;
try {
connection = druidDataSource.getConnection();
} catch (SQLException throwable) {
//抛异常了说明连接失效吗,则删除现有连接
log.error(throwable.getMessage());
flag = false;
delDataSources(datasourceId); // 删除数据源
}finally {
//如果连接正常记得关闭
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
}
if(!flag){
createDataSource(dataSource); // 创建数据源
}
}else {
createDataSource(dataSource); // 创建数据源
}
}
/**
*
* @param dataSource
*/
private void createDataSource(Datasource dataSource) { // 数据源对象
try {
String url="jdbc:mysql://"+dataSource.getIp(); // 数据库ip
String driverType="com.mysql.cj.jdbc.Driver";
url+="/"+dataSource.getDatabase()+"?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&serverTimezone=UTC&useSSL=false&zeroDateTimeBehavior=CONVERT_TO_NULL&allowPublicKeyRetrieval=true"; // 数据库
Class.forName(driverType);
Connection connection = DriverManager.getConnection(url, dataSource.getUsername(), dataSource.getPassword()); // 用户名 | 密码
if(connection==null){
log.error("数据源配置有错误DataSource{}",dataSource);
}else{
connection.close();
}
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setName(dataSource.getId().toString()); // 编号
druidDataSource.setDriverClassName(driverType);
druidDataSource.setUrl(url);
druidDataSource.setUsername(dataSource.getUsername()); // 用户名
druidDataSource.setPassword(dataSource.getPassword()); // 密码
druidDataSource.setMaxActive(20);
druidDataSource.setMinIdle(5);
//获取连接最大等待时间,单位毫秒
druidDataSource.setMaxWait(6000);
String validationQuery = "select 1";
//申请连接时执行validationQuery检测连接是否有效防止取到的连接不可用
druidDataSource.setTestOnBorrow(true);
druidDataSource.setValidationQuery(validationQuery);
druidDataSource.init();
this.customDataSources.put(dataSource.getId(),druidDataSource); // 编号
// 将map赋值给父类的TargetDataSources
setTargetDataSources(this.customDataSources); // 存储我们注册的数据源
// 将TargetDataSources中的连接信息放入resolvedDataSources管理
super.afterPropertiesSet();
} catch (Exception e) {
log.error("数据源创建失败",e);
}
}
/**
*
* @param datasourceId
*/
private void delDataSources(Long datasourceId) {
Map<Object, Object> map = this.customDataSources; // 存储我们注册的数据源
Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
for (DruidDataSource dataSource : druidDataSourceInstances) {
if (datasourceId.toString().equals(dataSource.getName())) {
map.remove(datasourceId);
//从实例中移除当前dataSource
DruidDataSourceStatManager.removeDataSource(dataSource);
// 将map赋值给父类的TargetDataSources
setTargetDataSources(map);
// 将TargetDataSources中的连接信息放入resolvedDataSources管理
super.afterPropertiesSet();
}
}
}
}

View File

@ -0,0 +1,44 @@
package com.muyu.networking.context;
import com.alibaba.ttl.TransmittableThreadLocal;
import lombok.extern.log4j.Log4j2;
/**
*
* @ClassName DataSourceContextHolder
* @Author
* @Date 2024/4/28 16:47
*/
@Log4j2
public class DataSourceContextHolder {
/**
* 线
*/
private static final TransmittableThreadLocal<Long> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
/**
*
*/
public static void setDataSource(Long datasourceId) {
CONTEXT_HOLDER.set(datasourceId);
log.info("已切换到数据源:{}",datasourceId);
}
/**
*
* @return
*/
public static Long getDataSource() {
return CONTEXT_HOLDER.get();
}
/**
*
*/
public static void removeDataSource() {
CONTEXT_HOLDER.remove();
log.info("已切换到主数据源");
}
}

View File

@ -0,0 +1,36 @@
package com.muyu.networking.controller;
import com.muyu.common.core.domain.Result;
import com.muyu.domain.datasources.SongInfo;
import com.muyu.networking.service.SongService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* songController
*
* @author GuanTieLin
* @date 2024-05-16
*/
@RestController
@RequestMapping("/song")
public class SongController {
@Autowired
private SongService songService; // songService接口
/**
*
*/
@GetMapping("/list")
public Result<List<SongInfo>> list() { // song_info对象
return Result.success(songService.list()); // 查询song列表
}
}

View File

@ -0,0 +1,47 @@
package com.muyu.networking.interceptor;
import com.muyu.networking.service.DataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName SaasInterceptor
* @Author GuanTieLin
* @Date 2024/6/3 21:09
*/
@Component
public class SaasInterceptor implements AsyncHandlerInterceptor {
@Autowired
private DataSourceService changeDataSourceService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 解决跨域问题
//1.如果请求不是动态的,handler对象不是HandlerMethod的实例(静态页面).放行
//2.如果请求是跨域请求(请求方法是:OPTIONS),handler对象不是HandlerMethod
if (!(handler instanceof HandlerMethod)) {
return true;
}
String datasourceCode = request.getHeader("datasource-code");
if(datasourceCode==null){
throw new RuntimeException("请求不合法");
}
changeDataSourceService.changeDS(Long.valueOf(datasourceCode));
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 切换为默认数据源
changeDataSourceService.toDefaultDS();
}
}

View File

@ -0,0 +1,16 @@
package com.muyu.networking.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.muyu.domain.datasources.Datasource;
import org.apache.ibatis.annotations.Mapper;
/**
* @ClassName DataSourceMapper
* @Author
* @Date 2024/6/3 20:53
*/
@Mapper
public interface DataSourceMapper extends BaseMapper<Datasource> { // 数据源对象
}

View File

@ -8,11 +8,11 @@ import org.apache.ibatis.annotations.Mapper;
* @ClassDescription:
* @JdkVersion: 17
* @Author: zhangxu
* @Created: 2024/5/31 16:21
* @Created: 2024/6/2 16:34
*/
@Mapper
public interface FenceMapper extends BaseMapper<Fences> {
int addFeace(Fences fences);
public interface FencesMapper extends BaseMapper<Fences> {
void add(Fences fences);
}

View File

@ -0,0 +1,17 @@
package com.muyu.networking.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.muyu.domain.datasources.SongInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* songMapper
*
* @author
* @date
*/
@Mapper
public interface SongMapper extends BaseMapper<SongInfo> { // song_info对象
}

View File

@ -0,0 +1,14 @@
package com.muyu.networking.service;
/**
* @ClassName
* @Author
* @Date 2024/4/28 18:46
*/
public interface DataSourceService {
void toDefaultDS();
boolean changeDS(Long datasourceId);
}

View File

@ -0,0 +1,23 @@
package com.muyu.networking.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.muyu.domain.datasources.SongInfo;
import java.util.List;
/**
* songService
*
* @author
* @date 2024-05-16
*/
public interface SongService extends IService<SongInfo> { // song_info对象
/**
* song
*
* @return song
*/
public List<SongInfo> list(); // song_info对象
}

View File

@ -0,0 +1,57 @@
package com.muyu.networking.service.impl;
import com.muyu.domain.datasources.Datasource;
import com.muyu.networking.config.DynamicRoutingDataSource;
import com.muyu.networking.context.DataSourceContextHolder;
import com.muyu.networking.mapper.DataSourceMapper;
import com.muyu.networking.service.DataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @ClassName
* @Author
* @Date
*/
@Service
public class DataSourceServiceImpl implements DataSourceService {
@Autowired
private DataSourceMapper dataSourceMapper;
@Autowired
private DynamicRoutingDataSource dynamicRoutingDataSource;
@Override
public void toDefaultDS() {
//切到默认数据源
DataSourceContextHolder.removeDataSource(); // 删除数据源
}
@Override
public boolean changeDS(Long datasourceId) {
//切到默认数据源
DataSourceContextHolder.removeDataSource(); // 删除数据源
//找到所有的配置
List<Datasource> dataSourceList = dataSourceMapper.selectList(null); // 数据源对象
if(!dataSourceList.isEmpty()){
for (Datasource dataSource : dataSourceList) { // 数据源对象
if(dataSource.getId().equals(datasourceId)){ // 编号
System.out.println("已找到数据源,datasourceId是" + dataSource.getId()); // 编号
//判断连接是否存在,不存在就创建
dynamicRoutingDataSource.checkCreateDataSource(dataSource); // 检查数据源是否已经创建
//切换数据源
DataSourceContextHolder.setDataSource(dataSource.getId()); // 编号 --> 切换数据源
return true;
}
}
}
return false;
}
}

View File

@ -53,7 +53,7 @@ public class FenceServiceImpl extends ServiceImpl<FencesMapper, Fences> implemen
public int insertFence(Fences fences) {
int count=0;
fences.setCreateBy(SecurityUtils.getUsername());
fences.setFenceName("电子围栏"+count++);
fences.setFenceName("电子围栏"+(count++));
fences.setCreateTime(new Date());
String string = fences.getPath().toString();
fences.setRadius(string);

View File

@ -0,0 +1,35 @@
package com.muyu.networking.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.muyu.domain.datasources.SongInfo;
import com.muyu.networking.mapper.SongMapper;
import com.muyu.networking.service.SongService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* songService
*
* @author
* @date 2024-05-16
*/
@Slf4j
@Service
public class SongServiceImpl extends ServiceImpl<SongMapper, SongInfo> implements SongService { // song_info对象 | songService接口
/**
* song
*
* @return song
*/
@Override // 查询song列表
public List<SongInfo> list() { // song_info对象
return this.list(new LambdaQueryWrapper<>());
}
}