first commit
This commit is contained in:
36
qiaoba-commons/qiaoba-common-datasource/pom.xml
Normal file
36
qiaoba-commons/qiaoba-common-datasource/pom.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>qiaoba-commons</artifactId>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<version>1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qiaoba-common-datasource</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Druid -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- Mysql Connector -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<!-- mybatis plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiaoba</groupId>
|
||||
<artifactId>qiaoba-common-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,83 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import com.alibaba.druid.pool.DruidDataSource;
|
||||
import com.qiaoba.common.database.entity.DynamicDataSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局跨域配置
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
@Configuration
|
||||
public class DynamicDataSourceConfig {
|
||||
/**
|
||||
* 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public static DynamicDataSourceContext dataSource() {
|
||||
Map<Object, Object> targetDataSource = getDataSource();
|
||||
//把DynamicDataSourceContext纳入容器管理
|
||||
DynamicDataSourceContext dynamicDataSourceContext = new DynamicDataSourceContext();
|
||||
dynamicDataSourceContext.freshDataSource(targetDataSource);
|
||||
return dynamicDataSourceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建初始化数据源 TODO 生成中去其他地方获取初始化数据源(例如:表里面获取)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Map<Object, Object> getDataSource() {
|
||||
|
||||
DynamicDataSource ds2 = new DynamicDataSource();
|
||||
ds2.setTenantCode("1");
|
||||
ds2.setDriver("com.mysql.cj.jdbc.Driver");
|
||||
ds2.setUrl("jdbc:mysql://121.5.136.69:3306/qiaoba-boot?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
|
||||
ds2.setUsername("root");
|
||||
ds2.setPassword("LpYN7LUoL?l0OSpR2");
|
||||
|
||||
DynamicDataSource ds1 = new DynamicDataSource();
|
||||
ds1.setTenantCode("2");
|
||||
ds1.setDriver("com.mysql.cj.jdbc.Driver");
|
||||
ds1.setUrl("jdbc:mysql://120.79.217.22:3306/qiaoba-boot?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");
|
||||
ds1.setUsername("root");
|
||||
ds1.setPassword("FeyZ7xZr6JtuKibm");
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
map.put(ds1.getTenantCode(), buildDataSource(ds1));
|
||||
map.put(ds2.getTenantCode(), buildDataSource(ds2));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把数据源对象组装成HikariDataSource
|
||||
*
|
||||
* @param DynamicDataSource
|
||||
* @return
|
||||
*/
|
||||
private static Object buildDataSource(DynamicDataSource DynamicDataSource) {
|
||||
DruidDataSource dataSource = new DruidDataSource();
|
||||
dataSource.setUrl(DynamicDataSource.getUrl());
|
||||
dataSource.setUsername(DynamicDataSource.getUsername());
|
||||
dataSource.setPassword(DynamicDataSource.getPassword());
|
||||
dataSource.setDriverClassName(DynamicDataSource.getDriver());
|
||||
dataSource.setInitialSize(5);
|
||||
dataSource.setMinIdle(10);
|
||||
dataSource.setMaxActive(20);
|
||||
try {
|
||||
dataSource.init();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return dataSource;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.qiaoba.common.database.config;
|
||||
|
||||
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源上下文对象
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2021/10/15 0015 下午 16:43
|
||||
*/
|
||||
public class DynamicDataSourceContext extends AbstractRoutingDataSource {
|
||||
|
||||
|
||||
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置默认数据源、全部数据源,及刷新
|
||||
*/
|
||||
public void freshDataSource(Map<Object, Object> targetDataSources) {
|
||||
//默认数据源
|
||||
super.setDefaultTargetDataSource(targetDataSources.get("master"));
|
||||
//设置全部数据源
|
||||
super.setTargetDataSources(targetDataSources);
|
||||
//刷新(即把targetDataSources刷到resolvedDataSources中去,resolvedDataSources才是我们真正存放数据源的map)
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object determineCurrentLookupKey() {
|
||||
//获取当前指定的数据源
|
||||
return getDataSource();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上下文中的数据源
|
||||
*/
|
||||
public String getDataSource() {
|
||||
return CONTEXT_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置上下文中的数据源
|
||||
*/
|
||||
public void setDataSource(String dataSource) {
|
||||
CONTEXT_HOLDER.set(dataSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除上下文中的数据源
|
||||
*/
|
||||
public void clearDataSource() {
|
||||
CONTEXT_HOLDER.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.qiaoba.common.database.entity;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 动态数据源实体
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @date 2023-04-24 21:47:48
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DynamicDataSource {
|
||||
|
||||
/**
|
||||
* 租户Code
|
||||
*/
|
||||
private String tenantCode;
|
||||
|
||||
/**
|
||||
* 数据库-url
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* 数据库-username
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 数据库-password
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 数据库-驱动
|
||||
*/
|
||||
private String driver;
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.qiaoba.common.database.factories;
|
||||
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceConfig;
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态数据源的工厂,主要解决缓存依赖
|
||||
*
|
||||
* @version 1.0
|
||||
* @author ailanyin
|
||||
* @since 2023-04-25 21:47:34
|
||||
*/
|
||||
@Configuration
|
||||
public class DynamicDataSourceFactory {
|
||||
|
||||
/**
|
||||
* 把DynamicDataSourceContext 纳入容器管理,其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象,并调用freshDataSource方法
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public static DynamicDataSourceContext dataSource() {
|
||||
Map<Object, Object> targetDataSource = DynamicDataSourceConfig.getDataSource();
|
||||
//把DynamicDataSourceContext纳入容器管理
|
||||
DynamicDataSourceContext dynamicDataSourceContext = new DynamicDataSourceContext();
|
||||
dynamicDataSourceContext.freshDataSource(targetDataSource);
|
||||
return dynamicDataSourceContext;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.qiaoba.common.database.filters;
|
||||
|
||||
import com.qiaoba.common.database.config.DynamicDataSourceContext;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 动态切换不同租户的数据源
|
||||
*
|
||||
* @author ailanyin
|
||||
* @version 1.0
|
||||
* @since 2023-04-25 22:48:43
|
||||
*/
|
||||
@Component
|
||||
@Order(-10000)
|
||||
public class HandleTenantFilter implements Filter {
|
||||
|
||||
@Resource
|
||||
private DynamicDataSourceContext dynamicDataSourceContext;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) servletRequest;
|
||||
String instanceId = request.getParameter("tenant");
|
||||
//设置当前租户对应的数据库
|
||||
dynamicDataSourceContext.setDataSource(instanceId);
|
||||
System.out.println("当前数据源是:" + dynamicDataSourceContext.getDataSource());
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
dynamicDataSourceContext.clearDataSource();
|
||||
}
|
||||
}
|
@ -0,0 +1,306 @@
|
||||
package com.qiaoba.common.database.mapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.*;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.qiaoba.common.web.utils.BeanCopyUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 自定义 Mapper 接口, 实现 自定义扩展
|
||||
*
|
||||
* @param <M> mapper 泛型
|
||||
* @param <T> table 泛型
|
||||
* @param <V> vo 泛型
|
||||
* @author ailanyin
|
||||
* @since 2023-04-23 17:20:32
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public interface BaseMapperPlus<M, T, V> extends BaseMapper<T> {
|
||||
|
||||
|
||||
/**
|
||||
* currentVoClass
|
||||
*
|
||||
* @return Vo
|
||||
*/
|
||||
default Class<V> currentVoClass() {
|
||||
return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* currentModelClass
|
||||
*
|
||||
* @return model
|
||||
*/
|
||||
default Class<T> currentModelClass() {
|
||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* currentMapperClass
|
||||
*
|
||||
* @return mapper
|
||||
*/
|
||||
default Class<M> currentMapperClass() {
|
||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有
|
||||
*
|
||||
* @return all lst
|
||||
*/
|
||||
default List<T> selectList() {
|
||||
return this.selectList(new QueryWrapper<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增
|
||||
*
|
||||
* @param entityList list
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean insertBatch(Collection<T> entityList) {
|
||||
return Db.saveBatch(entityList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
*
|
||||
* @param entityList list
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean updateBatchById(Collection<T> entityList) {
|
||||
return Db.updateBatchById(entityList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量插入或更新
|
||||
*
|
||||
* @param entityList list
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean insertOrUpdateBatch(Collection<T> entityList) {
|
||||
return Db.saveOrUpdateBatch(entityList);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量插入(包含限制条数)
|
||||
*
|
||||
* @param entityList list
|
||||
* @param batchSize 最大调数
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean insertBatch(Collection<T> entityList, int batchSize) {
|
||||
return Db.saveBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量更新(包含限制条数)
|
||||
*
|
||||
* @param entityList list
|
||||
* @param batchSize 最大调数
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean updateBatchById(Collection<T> entityList, int batchSize) {
|
||||
return Db.updateBatchById(entityList, batchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量插入或更新(包含限制条数)
|
||||
*
|
||||
* @param entityList list
|
||||
* @param batchSize 最大调数
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
|
||||
return Db.saveOrUpdateBatch(entityList, batchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 插入或更新
|
||||
* @param entity entity
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean insertOrUpdate(T entity) {
|
||||
return Db.saveOrUpdate(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询Vo by id
|
||||
* @param id 主键
|
||||
* @return Vo
|
||||
*/
|
||||
default V selectVoById(Serializable id) {
|
||||
return selectVoById(id, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 查询
|
||||
*
|
||||
* @param id 主键
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @return Vo
|
||||
*/
|
||||
default <C> C selectVoById(Serializable id, Class<C> voClass) {
|
||||
T obj = this.selectById(id);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
return BeanCopyUtil.copy(obj, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Ids 查询 list
|
||||
*
|
||||
* @param idList ids
|
||||
* @return list
|
||||
*/
|
||||
default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
|
||||
return selectVoBatchIds(idList, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Ids 查询 list
|
||||
*
|
||||
* @param idList ids
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @return list
|
||||
*/
|
||||
default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
|
||||
List<T> list = this.selectBatchIds(idList);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return BeanCopyUtil.copyList(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* selectVoByMap
|
||||
*
|
||||
* @param map map
|
||||
* @return Vo
|
||||
*/
|
||||
default List<V> selectVoByMap(Map<String, Object> map) {
|
||||
return selectVoByMap(map, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* selectVoByMap
|
||||
*
|
||||
* @param map map
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @return Vo
|
||||
*/
|
||||
default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
|
||||
List<T> list = this.selectByMap(map);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return BeanCopyUtil.copyList(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询一条记录
|
||||
*
|
||||
* @param wrapper 条件
|
||||
* @return Vo
|
||||
*/
|
||||
default V selectVoOne(Wrapper<T> wrapper) {
|
||||
return selectVoOne(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询一条记录
|
||||
*
|
||||
* @param wrapper 条件
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @return Vo
|
||||
*/
|
||||
default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
|
||||
T obj = this.selectOne(wrapper);
|
||||
if (ObjectUtil.isNull(obj)) {
|
||||
return null;
|
||||
}
|
||||
return BeanCopyUtil.copy(obj, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询所有
|
||||
*
|
||||
* @param wrapper 条件
|
||||
* @return list
|
||||
*/
|
||||
default List<V> selectVoList(Wrapper<T> wrapper) {
|
||||
return selectVoList(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件查询所有
|
||||
*
|
||||
* @param wrapper 条件
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @return list vo
|
||||
*/
|
||||
default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
|
||||
List<T> list = this.selectList(wrapper);
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return CollUtil.newArrayList();
|
||||
}
|
||||
return BeanCopyUtil.copyList(list, voClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询Vo
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param wrapper 条件
|
||||
* @param <P> 泛型
|
||||
* @return IPage
|
||||
*/
|
||||
default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
|
||||
return selectVoPage(page, wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询Vo
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param wrapper 条件
|
||||
* @param voClass Vo
|
||||
* @param <C> 泛型
|
||||
* @param <P> 泛型
|
||||
* @return IPage
|
||||
*/
|
||||
default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
|
||||
IPage<T> pageData = this.selectPage(page, wrapper);
|
||||
IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
|
||||
if (CollUtil.isEmpty(pageData.getRecords())) {
|
||||
return (P) voPage;
|
||||
}
|
||||
voPage.setRecords(BeanCopyUtil.copyList(pageData.getRecords(), voClass));
|
||||
return (P) voPage;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.qiaoba.common.database.config.DynamicDataSourceConfig,\
|
||||
com.qiaoba.common.database.filters.HandleTenantFilter
|
||||
|
||||
|
Reference in New Issue
Block a user