技术标签: spring java mybatis SpringBoot
项目中遇到一个多租户的需求,集团公司下有多个分公司,每个分公司的业务独立,但是管理上一样,并且集团公司可以动态添加分公司。现有三种方案:
方案一的优点:只需要一个数据库连接,数据库维护简单,可以动态添加新分公司。缺点:开发过程中需要将所有查询都添加上company_id,非常不方便,并且单表的数据量大,风险大。
方案二的优点:只需要一个数据库连接,各个分公司的数据独立。缺点:表维护麻烦,数据库挂掉,所有分公司数据都挂。
方案三的优点:每个分公司的数据独立,风险小,单库的数据量小。缺点:表维护麻烦。
经讨论后使用了第三种方案,每个分公司创建一个数据库。
在方案三的基础上,选择对应的技术框架,在网上找了下,有mycat数据库中间件。但是mycat无法动态添加新的数据库节点,最后选择了自己实现动态添加数据源的方式。
springboot启动的时候,创建一个默认的数据库连接(我创建的是集团的),然后从集团公司的数据库中获取分公司的数据库配置动态创建数据源中进行管理,通过当前登陆的用户选择数据源。
spring中提供了 AbstractRoutingDataSource 类进行多数据源选择,所以我们在springboot中只需要继承该类,重写选择数据源方法和设置数据源方法,并创建添加数据源方法,实现代码如下:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashMap;
import java.util.Map;
public class MultiRouteDataSource extends AbstractRoutingDataSource {
private static Map<Object, Object> targetDataSources = new HashMap<>();
@Override
protected Object determineCurrentLookupKey() {
// 通过绑定线程的数据源上下文实现多数据源的动态切换
return DataSourceContext.getDataSource();
}
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
MultiRouteDataSource.targetDataSources = targetDataSources;
}
/**
* 动态增加数据源
*
* @param name 数据源名称
* @param dataSource 数据源属性
* @return
*/
public synchronized void addDataSource(String name, HikariDataSource dataSource) {
try {
// 对该数据源进行测试
Map<Object, Object> targetMap = MultiRouteDataSource.targetDataSources;
targetMap.put(name, dataSource);
this.afterPropertiesSet();
} catch (Exception e) {
logger.error(e.getMessage());
}
}
}
补充DataSourceContext的内容
/**
* 数据源管理器Bean
*/
public class DataSourceContext {
/**
* 存储的是数据源的key
*/
private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();
/**
* Get current DataSource
*
* @return data source name
*/
public static String getDataSource() {
return HOLDER.get();
}
/**
* 设置数据源
*
* @param dataSource the dataSource
*/
public static synchronized void setDataSource(String dataSource) {
HOLDER.set(dataSource);
}
/**
* To set DataSource as default
*/
public static void clearDataSource() {
HOLDER.remove();
}
}
创建DataSource配置,该类主要实现的是多数据源的配置。
import com.zaxxer.hikari.HikariDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
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 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 {
/**
* DataSource 自动配置并注册
*
* @return data source
*/
@Bean("master")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource masterDataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setPoolName("master");
return dataSource;
}
/**
* 注册动态数据源
*
* @return
*/
@Bean(name = "multiDataSource")
public MultiRouteDataSource multiDataSource() {
MultiRouteDataSource dynamicRoutingDataSource = new MultiRouteDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("master", masterDataSource());
dynamicRoutingDataSource.setDefaultTargetDataSource(masterDataSource());// 设置默认数据源
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
/**
* session管理器
*
* @return
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(multiDataSource());
return sqlSessionFactoryBean;
}
/**
* 事务管理器
*
* @return the platform transaction manager
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(multiDataSource());
}
}
重点来了,下面代码可以实现在业务类中进行新增数据源和切换数据源。
@Test
public void createDataSource() {
try {
HikariDataSource devDataSource = new HikariDataSource();
devDataSource.setJdbcUrl("");
devDataSource.setDriverClassName(DRIVER_CLASS_NAME);
devDataSource.setUsername("");
devDataSource.setPassword("");
devDataSource.setMinimumIdle(dataSourcePoolProperties.getMinimumIdle());
devDataSource.setMaximumPoolSize(dataSourcePoolProperties.getMaximumPoolSize());
devDataSource.setAutoCommit(dataSourcePoolProperties.isAutoCommit());
devDataSource.setIdleTimeout(dataSourcePoolProperties.getIdleTimeout());
devDataSource.setPoolName(dataSourcePoolProperties.getPoolNamePrefix() + "test");
devDataSource.setMaxLifetime(dataSourcePoolProperties.getMaxLifetime());
devDataSource.setConnectionTimeout(dataSourcePoolProperties.getConnectionTimeout());
devDataSource.setConnectionTestQuery(dataSourcePoolProperties.getConnectionTestQuery());
HikariDataSource testDataSource = new HikariDataSource();
testDataSource.setJdbcUrl("");
testDataSource.setDriverClassName(DRIVER_CLASS_NAME);
testDataSource.setUsername("");
testDataSource.setPassword("");
testDataSource.setMinimumIdle(dataSourcePoolProperties.getMinimumIdle());
testDataSource.setMaximumPoolSize(dataSourcePoolProperties.getMaximumPoolSize());
testDataSource.setAutoCommit(dataSourcePoolProperties.isAutoCommit());
testDataSource.setIdleTimeout(dataSourcePoolProperties.getIdleTimeout());
testDataSource.setPoolName(dataSourcePoolProperties.getPoolNamePrefix() + "test");
testDataSource.setMaxLifetime(dataSourcePoolProperties.getMaxLifetime());
testDataSource.setConnectionTimeout(dataSourcePoolProperties.getConnectionTimeout());
testDataSource.setConnectionTestQuery(dataSourcePoolProperties.getConnectionTestQuery());
// 从容器中获取动态数据源的Bean,这儿也可以通过注解的方式获取
MultiRouteDataSource multiDataSource = (MultiRouteDataSource) SpringContextUtil.getBean("multiDataSource");
// 添加数据源
multiDataSource.addDataSource("dev", devDataSource);
multiDataSource.addDataSource("test", testDataSource);
// 切换数据源
DataSourceContext.setDataSource("test");
// 测试
List<SysUser> sysUsers = sysUserService.getList(null);
log.info(sysUsers.get(0).getName());
} catch (Exception e) {
log.error(e);
}
}
基本代码已经完成,请多指教!
1905: 小火山的跳子游戏Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 437 Solved: 95Description 小火山和火山火山在一块玩跳子游戏。规则如下: 1:跳子的起始位置为0,棋盘大小从1到N 2:每次跳子跳k步。 例如当前位置为i, 那么下一步为i + k 3:跳子过程中,碰到1
【LeetCode】第412题——Fizz Buzz(难度:简单)题目描述解题思路代码详解注意点题目描述写一个程序,输出从 1 到 n 数字的字符串表示。如果 n 是3的倍数,输出“Fizz”;如果 n 是5的倍数,输出“Buzz”;3.如果 n 同时是3和5的倍数,输出 “FizzBuzz”。示例:n = 15,返回:[ “1”, “2”, “Fizz”, “4”, “Buzz”, “Fizz”, “7”, “8”, “Fizz”, “Buzz”, “11”, “Fizz”
前言产品大佬又提需求啦,要求app里面的图表要实现白天黑夜模式的切换,以满足不同光线下都能保证足够的图表清晰度. 怎么办?可能解决的办法很多,你可以给图表view增加一个toggle方法,参数String,day/night,然后切换之后postInvalidate 刷新重绘.OK,可行,但是这种方式切换白天黑夜,只是单个View中有效,那么如果哪天产品又要另一个View换肤,难道我要一...
L2TP VPN:二层VPN,用于远程访问C/S结构,L2TP VPN是一种用于承载PPP报文的隧道技术,主要用于在远程办公场景中为出差员工远程访问企业内网资源提供接入服务。L2TP不支持加密L2TP使用的端口号1701L2TP协议以UDP头部封装目的:出差员工跨越Internet远程访问企业内网资源时需要使用PPP协议向企业总部申请内网IP地址,并供总部对出差员工进行身份认证。但PPP报文受其协议自身的限制无法在Internet上直接传输。于是,PPP报文的传输问题成为了制约出差员工远程办公的
因为项目有一个新的需求,需要修改openstack中每次新建租户后的默认安全组规则。首先先来分析一下:我用的openstack版本为queens。每次新建租户后本来是没有安全组的,当使用新租户第一次访问dashboard的安全组列表或者调用API时,openstack会自动的为该租户创建一个default安全组,有4条规则。如图大意就是1)允许使用该安全组的虚拟机向外部发送一切...
转:http://blog.csdn.net/han____shuai/article/details/50700882题目Heart Beat Period Detection背景交代In clinic, it is frequently required to detect heart rate. Theheart rate is usually computed from
Python基础教程学习路线,如果想从零基础熟练掌握python开发,你需要有一套完整的学习路线,学习Python的优势如何学习等相关的内容。接下来小编带你一起寻找Python 基础教程学习路线~一、学习Python的优势1.易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单。2.易于阅读:Python代码定义的更清晰。3.易于维护:Python的成功在于它...
如果单单是因为配置问题的话,各位可以在网上找到有关配置的教程。而我想说的是在你配置没有问题的时候,却还是一直出现这个异常的处理,以下是我出异常的代码(我的配置是没有问题的,和网上大多教程一样):@Repository("accountDaoImpl")public class AccountDaoImpl implements AccountDao{ @Autowired ...
解析:依题意,改地址段共有BFFFH - A000H + 1 = 2000H = 2×16³ = 2³,又因为按字节编制,1个字节即一个存储空间,因此共 2^13 个存储单元,又1KB=1024B,答案即8K。解析:相联存储器也称为按内容访问存储器,是一种根据存储内容(不根据地址) 来进行存取的存储器,以实现快速地查找快表。流水线方式:单条指令所需时间+(n-1)×(流水线周期),其...
这是一款腾讯UED设计的提示插件,看着肯定很眼熟吧,闲话不多说,直接上demo吧。 显示 ZENG.msgbox.show(提示信息,图标类型); 隐藏: ZENG.msgbox._h...
提示未能加载文件或程序集System.EnterpriseServices未能加载文件或程序集“System.EnterpriseServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”或它的某一个依赖项。 解决方法: 方法一: C:\WINDOWS\Microsoft.NET\Fr...
获取典型常用数据摘要聚宽数据获取指数成分股获取股票行情数据获取股票财务数据自测与自学聚宽数据在聚宽数据这个页面可以看到聚宽平台集成好的各大类数据,如下图,点击可以查看详情与用法。但实际上可能有些数据要在API文档里才比较容易能找到,比如龙虎榜数据等。这时用ctrl+f进行网页搜索可以快速搜索需要的数据。接下来会介绍几种常用数据的取用方法,这些取用方法比较典型,掌握后能覆盖基本的数据需求以及较容易的...