-
2020-11-03 18:55:30
Java项目中多数据源动态切换的实现
前言
从事餐饮软件项目,作为阿里本地生活的一份子,从19年下半年开始阿里口碑一批P6 P7开发人员陆续加入开发,此处的话题是一个餐饮pos机项目上的问题。每个餐饮门店都有一台或多台pos机,主要架构上是C++语言作为前端展示,Java开发的jar包程序作为其后端进行业务处理,pos可以有多台而Java后台只有一个,考虑到轻量,选用了sqlite数据库
sqlite每个数据库都是以单个文件的形式存在,每个文件都是一个数据库,文件不宜过大,我们根据业务含义将数据库划分成了4个业务文件,请注意这里不是分库分表,每个文件中表结构都不同,那项目在运行过程中读取不同数据库就需要做到无缝切换,不仅如此,这些数据库文件是有版本的,是可以不段生成的,当新版本的数据库文件生成时我们就需要连接新版本的数据库文件,即动态增加数据源的连接前提
springBoot框架环境
项目里的多数据源含义
一个项目里,可以同时访问多个不同的数据库
实现
数据源切换的一般实现(推荐)
- 可以直接用原生的jdbc DriverManager. getConnection直接连接,但这种方式没有连接池且对于我们来说需要使用mybatis框架
- 将存放多个数据源的map(key是自定义的数据源id,value是java DataSource的实体)给到spring提供的AbstractRoutingDataSource并指定默认数据源(可以在determineCurrentLookupKey获取不到数据源时使用默认的数据源)
- 继承spring的AbstractRoutingDataSource接口并重写determineCurrentLookupKey方法,通过threadLocal获取当前线程的数据源id
- 项目启动时SqlSessionFactory的dataSource指定为重写的AbstractRoutingDataSource业务代码中手动指定当前线程threadLocal的数据源id
两个问题
- 不同数据源的事务不能同时回滚
- 无法动态的新增数据源
解决方案
如何自动切换
- 每个数据源都配置一套SqlSessionFactory,每个SqlSessionFactory都指定自己的mapper
- 不用手动指定数据源,但是SqlSessionFactory有多个
每个数据源的配置可以参考下面示例
@Configuration @PropertySource("classpath:/dataSourceConfig.properties") @MapperScan(basePackages = "com.alsc.basic.offline.sdk.dao.mapper.record", sqlSessionTemplateRef = "recordSqlSessionTemplate") @Slf4j public class MyBatisConfigRecord { @Autowired DataSourcePathService dataSourcePathService; /** * 加载数据源配置 */ @Bean(name = "recordDataSourceProperties") @ConfigurationProperties("spring.datasource.record") public DataSourceProperties recordDataSourceProperties() { return new DataSourceProperties(); } /** * 加载数据源 */ @Bean(name = "recordDataSource") HikariDataSource recordDataSource( @Qualifier("recordDataSourceProperties") DataSourceProperties dataSourceProperties) { String dataSourceAbsolutePath = ""; //这里你要自己定义路径 String url = "jdbc:sqlite:" + dataSourceAbsolutePath; dataSourceProperties.setUrl(url); return dataSourceProperties.initializeDataSourceBuilder() .type(HikariDataSource.class) .build(); } @Bean(name = "recordSessionFactory") public SqlSessionFactory recordSessionFactory(@Qualifier("recordDataSource") DataSource dataSource) throws Exception { return getSqlSessionFactory(dataSource, new String[] {"classpath*:mapper/record/*.xml"}); } @Bean(name = "recordSqlSessionTemplate") public SqlSessionTemplate recordSqlSessionTemplate( @Qualifier("recordSessionFactory") SqlSessionFactory recordSessionFactory) throws Exception { return new SqlSessionTemplate(recordSessionFactory); } private SqlSessionFactory getSqlSessionFactory(DataSource dataSource, String[] mapperLocations) throws Exception { MybatisProperties properties = new MybatisProperties(); properties.setMapperLocations(mapperLocations); SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(properties.resolveMapperLocations()); org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration(); configuration.setMapUnderscoreToCamelCase(true); bean.setConfiguration(configuration); return bean.getObject(); } }
如何动态替换
问题:当前数据库存在版本问题,数据库名称相同但存放在不同的版本目录下,需将当前版本的数据源切换url指向另一个版本
解决:重新生成一个DataSource对象和Environment对象指定给SqlSessionFactory的Configuration示例代码如下
private void changeDataSource(String version, SqlSessionFactory sessionFactory, DataSourceIdEnum dataSourceIdEnum, DataSourceProperties properties) { String dataSourceAbsolutePath = dataSourcePathService.getDataSourceAbsolutePath(dataSourceIdEnum); String url = "jdbc:sqlite:" + dataSourceAbsolutePath; properties.setUrl(url); HikariDataSource hikariDataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build(); DataSource oldDataSource = sessionFactory.getConfiguration().getEnvironment().getDataSource(); /** * 此处environment属性id,目前了解的主要作用在mybatis的一级缓存 * 生成CacheKey会用到,即使所有的id都设置相同,生成的CacheKey可能相同 * 由于不同数据源走不同sqlSession,不会有影响 */ Environment environment = new Environment(dataSourceIdEnum.getCode() + version, new JdbcTransactionFactory(), hikariDataSource); sessionFactory.getConfiguration().setEnvironment(environment); closeDataSource(oldDataSource); }
SqlSessionFactory的Configuration就是mybatis的配置信息,我们常用的配置如:下划线驼峰命名转换,新增记录返回自动生成的主键等都在这里
此处没有使用事务事务同步回滚
基本思路
我们可以获取事务方法中所有需要回滚的DataSourceTransactionManager,依次手动回滚
Mybatis手动事务回滚的一般代码实现
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus transactionStatus = transactionManager.getTransaction(def); transactionManager.rollback(transactionStatus);
问题
真正在实现这个demo的时候发现,在第二个数据源的事务回滚时会报错:Transaction synchronization is not active() (事务同步没有被激活)
分析
- 事务同步的使用一般是用TransactionSynchronizationManager.registerSynchronization向事务管理器中注册一个实现了TransactionSynchronization接口的类,该接口里提供了围绕事务执行的前置和后置增强方法
- Spring中一个线程中的多个事务应该是按照嵌套事务来设计的
- 上面的transactionManager.getTransaction执行时,Spring源码中会执行一个startTransaction方法,里面就开始一个事务,会判断ThreadLocal<Set> synchronizations是否为空,不为空就会生成一个挂起资源,第一个事务的挂起资源为空
- 在startTransaction中会给synchronizations赋值,即激活事务同步,也就是每个事务开始后都会初始化synchronizations,但下一个事务会读取当前线程的synchronizations并挂起,当做嵌套的事务
因此当我们执行完第一个事务的回滚后,由于第一个事务没有事务同步的挂起资源,代码会将该事务当做内嵌事务的最外层来处理,会清空synchronizations即关闭事务同步,再执行第二个事务的回滚,由于当前线程的synchronizations已被清空,所以会抛出事务同步没有激活的异常
解决
- 获取jdbc的connection回滚
@Aspect @Component @Slf4j public class MultiDataSourceTransactionAspectConnection { /** * 多数据源组合事务 * */ @Pointcut("@annotation(com.alsc.basic.offline.sdk.dao.annotation.TransactionalGroup)") public void transactionalGroupAspect() {}; @Around(value = "transactionalGroupAspect()") public Object transactionalGroupAspectAround(ProceedingJoinPoint pjp) throws Throwable { log.info("[多数据源组合事务切面]: 进入多数据源组合事务管理切面"); Object result = null; List<Connection> transactionConnectionList = new ArrayList<>(); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method targetMethod = methodSignature.getMethod(); TransactionalGroup transactionalGroup = targetMethod.getAnnotation(TransactionalGroup.class); AssertUtil.isNotNull(transactionalGroup,"[多数据源组合事务切面]: TransactionalGroup注解不能为空"); DataSourceIdEnum[] dataSourceIdEnums = transactionalGroup.value(); for(DataSourceIdEnum dataSourceIdEnum : dataSourceIdEnums){ DataSourceTransactionManager transactionManager = (DataSourceTransactionManager) SpringContextUtil.getBean(TransactionManagerEnum .getByDataSourceId(dataSourceIdEnum).getTransactionManagerBeanName()); AssertUtil.isNotNull(transactionManager, CommonErrorCode.NULL_PARAMETER, "[多数据源组合事务切面]: 从spring获取transactionManager为空"); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); Connection connection = transactionManager.getDataSource().getConnection(); connection.setAutoCommit(false); transactionConnectionList.add(connection); } try { result = pjp.proceed(); }catch (Throwable e){ if(!CollectionUtils.isEmpty(transactionConnectionList)){ Iterator<Connection> iterator = transactionConnectionList.iterator(); while (iterator.hasNext()){ Connection connection = iterator.next(); connection.rollback(); } } } log.info("[多数据源组合事务切面]: 多数据源组合事务管理切面结束"); return result; } }
- 由事务开始的顺序倒叙执行事务回滚
Spring事务源码解读:每个事务开始的时候都会初始化事务同步,下一个新事务会生成一个挂起资源类,前一个事务的同步(TransactionSynchronization)就被存到资源中并清空threadLocal,当然后面又会重新初始化threadLocal,在每个事务回滚或提交结束后会判断当前事务有无挂起资源,若有就重新恢复挂起的事务同步,这样下一个事务执行回滚或提交就会执行这些事务同步,就不会出现事务同步被清空的情况
这种方式其实就是按照嵌套事务回滚的思路解决
@Aspect @Component @Slf4j public class MultiDataSourceTransactionAspectDemo1 { /** * 多数据源组合事务 * */ @Pointcut("@annotation(com.alsc.basic.offline.sdk.dao.annotation.TransactionalGroup)") public void transactionalGroupAspect() {}; @Around(value = "transactionalGroupAspect()") public Object transactionalGroupAspectAround(ProceedingJoinPoint pjp) throws Throwable { log.info("[多数据源组合事务切面]: 进入多数据源组合事务管理切面"); Object result = null; List<TransactionManageDTO> transactionManagerDTOList = new ArrayList<>(); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method targetMethod = methodSignature.getMethod(); TransactionalGroup transactionalGroup = targetMethod.getAnnotation(TransactionalGroup.class); DataSourceIdEnum[] dataSourceIdEnums = transactionalGroup.value(); for(DataSourceIdEnum dataSourceIdEnum : dataSourceIdEnums){ DataSourceTransactionManager transactionManager = (DataSourceTransactionManager) SpringContextUtil.getBean(TransactionManagerEnum .getByDataSourceId(dataSourceIdEnum).getTransactionManagerBeanName()); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus transactionStatus = transactionManager.getTransaction(def); addtransactionManagerList(transactionManagerDTOList, dataSourceIdEnum, transactionManager, transactionStatus); } try { result = pjp.proceed(); }catch (Throwable e){ Collections.reverse(transactionManagerDTOList); for(TransactionManageDTO transactionManageDTO : transactionManagerDTOList){ DataSourceTransactionManager dataSourceTransactionManager = transactionManageDTO.getDataSourceTransactionManager(); TransactionStatus transactionStatus = transactionManageDTO.getTransactionStatus(); dataSourceTransactionManager.rollback(transactionStatus); } } log.info("[多数据源组合事务切面]: 多数据源组合事务管理切面结束"); return result; } private void addtransactionManagerList(List<TransactionManageDTO> transactionManagerDTOList, DataSourceIdEnum dataSourceIdEnum, DataSourceTransactionManager transactionManager, TransactionStatus transactionStatus) { TransactionManageDTO transactionManageDTO = new TransactionManageDTO(); transactionManageDTO.setDataSourceId(dataSourceIdEnum); transactionManageDTO.setDataSourceTransactionManager(transactionManager); transactionManageDTO.setTransactionStatus(transactionStatus); transactionManagerDTOList.add(transactionManageDTO); } }
- 多个事务执行 transactionManager.getTransaction后(即开始事务后)都执行一遍TransactionSynchronizationManager.clear() 这样下一个事务再开始时就不会有挂起的资源,都相当于最外层事务,我们理解为完全独立的事务,回滚可以按原顺序执行但每个事务回滚后都要再执行一遍TransactionSynchronizationManager.initSynchronization(); 重新初始化一遍,但是这样就不能使用自定义的事务同步
这种方式虽然不能使用事务同步,是一种退而求其次的方式,但若没有对事务同步有强要求的也可以采取
@Aspect @Component @Slf4j public class MultiDataSourceTransactionAspectDemo2 { /** * 多数据源组合事务 * */ @Pointcut("@annotation(com.alsc.basic.offline.sdk.dao.annotation.TransactionalGroup)") public void transactionalGroupAspect() {}; @Around(value = "transactionalGroupAspect()") public Object transactionalGroupAspectAround(ProceedingJoinPoint pjp) throws Throwable { log.info("[多数据源组合事务切面]: 进入多数据源组合事务管理切面"); Object result = null; List<TransactionManageDTO> transactionManagerDTOList = new ArrayList<>(); MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); Method targetMethod = methodSignature.getMethod(); TransactionalGroup transactionalGroup = targetMethod.getAnnotation(TransactionalGroup.class); DataSourceIdEnum[] dataSourceIdEnums = transactionalGroup.value(); for(DataSourceIdEnum dataSourceIdEnum : dataSourceIdEnums){ DataSourceTransactionManager transactionManager = (DataSourceTransactionManager) SpringContextUtil.getBean(TransactionManagerEnum .getByDataSourceId(dataSourceIdEnum).getTransactionManagerBeanName()); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus transactionStatus = transactionManager.getTransaction(def); addtransactionManagerList(transactionManagerDTOList, dataSourceIdEnum, transactionManager, transactionStatus); TransactionSynchronizationManager.clear(); } try { result = pjp.proceed(); }catch (Throwable e){ for(TransactionManageDTO transactionManageDTO : transactionManagerDTOList){ TransactionSynchronizationManager.initSynchronization(); DataSourceTransactionManager dataSourceTransactionManager = transactionManageDTO.getDataSourceTransactionManager(); TransactionStatus transactionStatus = transactionManageDTO.getTransactionStatus(); dataSourceTransactionManager.rollback(transactionStatus); } } log.info("[多数据源组合事务切面]: 多数据源组合事务管理切面结束"); return result; } private void addtransactionManagerList(List<TransactionManageDTO> transactionManagerDTOList, DataSourceIdEnum dataSourceIdEnum, DataSourceTransactionManager transactionManager, TransactionStatus transactionStatus) { TransactionManageDTO transactionManageDTO = new TransactionManageDTO(); transactionManageDTO.setDataSourceId(dataSourceIdEnum); transactionManageDTO.setDataSourceTransactionManager(transactionManager); transactionManageDTO.setTransactionStatus(transactionStatus); transactionManagerDTOList.add(transactionManageDTO); } }
demo用到的辅助类和注解
@Data @ToString public class TransactionManageDTO { private DataSourceIdEnum dataSourceId; private DataSourceTransactionManager dataSourceTransactionManager; private TransactionStatus transactionStatus; } @Target({ ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface TransactionalGroup { /** * * 配置的数据源id * */ DataSourceIdEnum[] value(); /******* 以下参数,参见 @Transactional ********/ //事务传播行为 Propagation propagation() default Propagation.REQUIRED; //事务隔离级别 Isolation isolation() default Isolation.DEFAULT; //哪些异常回滚 Class<? extends Throwable>[] rollbackFor() default {}; // 哪些异常不回滚事务 Class<? extends Throwable>[] noRollbackFor() default {}; }
更多相关内容 -
【WPS表格】生成图表后,数据源要改动,图表上的数据怎么同步
2022-01-30 22:27:34 -
hibernate动态多数据源
2021-05-07 11:39:22这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的...,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入...hibernate动态多数据源包括自动建表
网上搜了不少,只有多数据源的配置,但最主要的自动建表没有,我研究清楚记录下
hibernate 动态多数据源配置
hibernate的xml配置文件,设置多个数据源
<bean id="dataSourceOne" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${hibernate.connection.url}" /> <property name="username"><value>${hibernate.connection.username}</value></property> <property name="password" value="${hibernate.connection.password}" /> <property name="filters"><value>stat</value></property> <property name="maxActive"><value>100</value></property> <property name="initialSize"><value>1</value></property> <property name="maxWait"><value>60000</value></property> <property name="minIdle"><value>1</value></property> <property name="timeBetweenEvictionRunsMillis"><value>60000</value></property> <property name="minEvictableIdleTimeMillis"><value>300000</value></property> <property name="validationQuery"><value>SELECT 'x'</value></property> <property name="testWhileIdle"><value>true</value></property> <property name="testOnBorrow"><value>false</value></property> <property name="testOnReturn"><value>false</value></property> <property name="poolPreparedStatements"><value>true</value></property> <property name="maxOpenPreparedStatements"><value>20</value></property> </bean> <bean id="dataSourceTwo" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="${hibernate.connection.url2}" /> <property name="username"><value>${hibernate.connection.username}</value></property> <property name="password" value="${hibernate.connection.password}" /> <property name="filters"><value>stat</value></property> <property name="maxActive"><value>100</value></property> <property name="initialSize"><value>1</value></property> <property name="maxWait"><value>60000</value></property> <property name="minIdle"><value>1</value></property> <property name="timeBetweenEvictionRunsMillis"><value>60000</value></property> <property name="minEvictableIdleTimeMillis"><value>300000</value></property> <property name="validationQuery"><value>SELECT 'x'</value></property> <property name="testWhileIdle"><value>true</value></property> <property name="testOnBorrow"><value>false</value></property> <property name="testOnReturn"><value>false</value></property> <property name="poolPreparedStatements"><value>true</value></property> <property name="maxOpenPreparedStatements"><value>20</value></property> </bean>
新增一个动态数据源bean
<bean class="com.test.common.dao.DynamicDataSource" id="dataSourceTarget"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry value-ref="dataSourceOne" key="one"></entry> <entry value-ref="dataSourceTwo" key="two"></entry> </map> </property> <property name="debug" value="true"/> <property name="defaultTargetDataSource" ref="dataSourceOne" ></property> </bean>
然后是sessionFactory的设置
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSourceTarget" /> <property name="namingStrategy"> <bean class="org.hibernate.cfg.ImprovedNamingStrategy" /> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">${hibernate.dialect}</prop> <prop key="hibernate.show_sql">${hibernate.show_sql}</prop> <prop key="hibernate.format_sql">${hibernate.format_sql}</prop> <prop key="hibernate.generate_statistics">false</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> <property name="packagesToScan"> <list> <value>com.test.**.bean</value> </list> </property> </bean>
可以看到动态数据源使用了重新编写的类,具体代码如下
public class DynamicDataSource extends AbstractRoutingDataSource { private boolean debug = false; Logger log = Logger.getLogger(DynamicDataSource.class); private Map<Object, Object> dynamicTargetDataSources; private Object dynamicDefaultTargetDataSource; @Override protected Object determineCurrentLookupKey() { String datasource = DBContextHolder.getDataSource(); if (debug) { if (StringUtils.isEmpty(datasource)) { log.info("---当前数据源:默认数据源---"); } else { log.info("---当前数据源:" + datasource + "---"); } } return datasource; } @Override public void setTargetDataSources(Map<Object, Object> targetDataSources) { super.setTargetDataSources(targetDataSources); this.dynamicTargetDataSources = targetDataSources; } // 创建数据源 public boolean createDataSource(String key, String driveClass, String url, String username, String password) { try { try { // 排除连接不上的错误 Class.forName(driveClass); DriverManager.getConnection(url, username, password);// 相当于连接数据库 } catch (Exception e) { return false; } @SuppressWarnings("resource") DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setName(key); druidDataSource.setDriverClassName(driveClass); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); druidDataSource.setMaxWait(60000); druidDataSource.setFilters("stat"); DataSource createDataSource = (DataSource) druidDataSource; druidDataSource.init(); Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; dynamicTargetDataSources2.put(key, createDataSource);// 加入map setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理 return true; } catch (Exception e) { log.info(e + ""); return false; } } // 删除数据源 public boolean delDatasources(String datasourceid) { Map<Object, Object> dynamicTargetDataSources2 = this.dynamicTargetDataSources; if (dynamicTargetDataSources2.containsKey(datasourceid)) { Set<DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances(); for (DruidDataSource l : druidDataSourceInstances) { if (datasourceid.equals(l.getName())) { System.out.println(l); dynamicTargetDataSources2.remove(datasourceid); DruidDataSourceStatManager.removeDataSource(l); setTargetDataSources(dynamicTargetDataSources2);// 将map赋值给父类的TargetDataSources super.afterPropertiesSet();// 将TargetDataSources中的连接信息放入resolvedDataSources管理 return true; } } return false; } else { return false; } } // 测试数据源连接是否有效 public boolean testDatasource(String key, String driveClass, String url, String username, String password) { try { Class.forName(driveClass); DriverManager.getConnection(url, username, password); return true; } catch (Exception e) { return false; } } /** * Specify the default target DataSource, if any. * <p> * The mapped value can either be a corresponding * {@link javax.sql.DataSource} instance or a data source name String (to be * resolved via a {@link #setDataSourceLookup DataSourceLookup}). * <p> * This DataSource will be used as target if none of the keyed * {@link #setTargetDataSources targetDataSources} match the * {@link #determineCurrentLookupKey()} current lookup key. */ public void setDefaultTargetDataSource(Object defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); this.dynamicDefaultTargetDataSource = defaultTargetDataSource; } /** * @param debug * the debug to set */ public void setDebug(boolean debug) { this.debug = debug; } /** * @return the debug */ public boolean isDebug() { return debug; } /** * @return the dynamicTargetDataSources */ public Map<Object, Object> getDynamicTargetDataSources() { return dynamicTargetDataSources; } /** * @param dynamicTargetDataSources * the dynamicTargetDataSources to set */ public void setDynamicTargetDataSources(Map<Object, Object> dynamicTargetDataSources) { this.dynamicTargetDataSources = dynamicTargetDataSources; } /** * @return the dynamicDefaultTargetDataSource */ public Object getDynamicDefaultTargetDataSource() { return dynamicDefaultTargetDataSource; } /** * @param dynamicDefaultTargetDataSource * the dynamicDefaultTargetDataSource to set */ public void setDynamicDefaultTargetDataSource(Object dynamicDefaultTargetDataSource) { this.dynamicDefaultTargetDataSource = dynamicDefaultTargetDataSource; } @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } }
这段参考了网上记不清哪个的了,还可以调用代码增加或者删除数据源。然后是切换的数据源的方法类
/** * 数据源切换 * * */ public class DBContextHolder { // 对当前线程的操作-线程安全的 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); // 调用此方法,切换数据源 public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } // 获取数据源 public static String getDataSource() { return contextHolder.get(); } // 删除数据源 public static void clearDataSource() { contextHolder.remove(); } }
public class DataSourceConst { public static final String ONE="one"; public static final String TWO="two"; }
具体使用DBContextHolder 的setDataSource方法进行切换
DBContextHolder.setDataSource(DataSourceConst.TWO);
注意,切换数据源,只在当前线程生效
自动建表
启动时,因为设置了默认数据源,自动建表会自动为默认数据源建表。可其他数据源就不会自动建表了,这个比较坑,就需要在启动时切换数据源,并运行自动建表的方法,具体如下
@Component("firstSchedulingService") public class FirstSchedulingService implements ApplicationListener<ContextRefreshedEvent>{ @Autowired private LocalSessionFactoryBean sessionFactory; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // TODO Auto-generated method stub DBContextHolder.setDataSource(DataSourceConst.TWO); sessionFactory.updateDatabaseSchema(); } }
多数据源的问题基本都解决了,可感觉还是很蛋疼,每次切换只在当前线程生效,这个需要每个操作都切换一次数据源,要不都是操作默认数据。再想想项目的数据真心没有很多,就放弃了。这里记录下,以后万一用的到。
-
新增支持Hive数据源,引入新图表类型词云图,DataEase开源数据可视化分析平台v1.5.0发布
2021-12-06 11:11:01数据集新增同步字段功能,仪表板支持视频组件。
12月6日,DataEase开源数据可视化分析平台正式发布v1.5.0版本。在该版本中,新增了对Hive数据源的支持;视图方面,图表类型增加了对词云图的支持;视图编辑的操作上,提供了重置功能,并取消了之前版本的实时保存功能;数据集方面新增同步字段功能,解决了由于数据集变化导致的视图不可用的问题;仪表板方面,增加了对视频组件的支持。另外,我们还对其他一些常用的功能进行了功能优化和问题修复。新增功能
■ 数据源新增支持Hive
截至DataEase v1.4版本,DataEase支持的数据源包括Excel、MySQL、Oracle、SQL Server、PostgreSQL、Elasticsearch、MariaDB、Apache Doris、ClickHouse、MongoDB、Amazon Redshift等。在DataEase v1.5.0版本中,我们增加了对Hive数据源的支持。
Hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,是一种可以存储、查询和分析存储在Hadoop中大规模数据的机制。Hive的优点是学习成本低,可以通过类似SQL语句实现快速MapReduce统计,使MapReduce变得更加简单,而不必开发专门的MapReduce应用程序。
■ 数据集新增同步字段功能
在之前版本的DataEase中,用户编辑数据集时,不论是否对原有字段进行修改,都可能会造成诸如视图无法显示等异常情况,且数据集表结构的变化也无法更新到之前的数据集中。DataEase v1.5.0版本中,在数据集管理页新增了同步字段功能,用户在修改了数据库表结构后,能够在DataEase中将更新的字段信息同步过来,解决之前版本中所存在的问题。
■ 视图增加对词云图的支持
视图图表的支持对于数据可视化工具来说至关重要,DataEase一直坚持在持续迭代的每个版本中不断增加对新图表类型的支持。在DataEase v1.5.0版本中,增加了新的图表类型——词云图。词云图由词汇组成类似云的彩色图形,用于展示大量文本数据。例如,制作用户画像、对用户进行聚类,实现精细化营销。
■ 仪表板支持视频组件
多媒体元素对于数据可视化来说也是非常重要的组成部分,它可以丰富可视化大屏的展示形式,从另一个维度表达出数据所不能表达的内容。在DataEase v1.5.0版本中,新增了视频组件,可以实现在仪表板中播放mp4、webm格式的视频影像,满足了用户对多媒体展现形式的需求。
除了上述提到的新增功能外,DataEase v1.5.0版本还包含了很多其他的功能更新和优化,欢迎进入我们的官方文档及GitHub仓库的Release页面查看更加详细的更新日志。
功能优化
■ refactor(数据集):修复编辑数据集导致字段删除重建的问题;
■ refactor(数据集):优化数据集查询;
■ refactor(仪表板):移除仪表板自动刷新时的“Loading”动画;
■ refactor(仪表板):优化仪表板PDF的导出,防止导出画面不全;
■ refactor(仪表板):优化组件间隙和仪表板画布间隙;
■ refactor(仪表板):增加仪表板编辑快捷入口;
■ refactor(仪表板):增加矩形、时间组件的属性设置项;
■ refactor(仪表板):优化仪表板上移、下移、置顶、置底移动算法,可以实时得到移动效果,解决置顶不准确的问题;
■ refactor:规范化SQL格式及数据表编码;
■ refactor:前端代码去掉不必要的引用,加快编译速度。
Bug修复
■ fix:修复图表类型切换逻辑错误的Bug;
■ fix:修复SQL Server计算字段的Bug;
■ fix:修复取消短链接的Bug;
■ fix:修复源码模式运行时,视图类型样式错乱的问题;
■ fix:修复PostgreSQL时间类型转换的Bug;
■ fix:修复Token验签逻辑错误的Bug;
■ fix:修复多个时间控件相互影响的Bug;
■ fix:修复MongoDB数据源校验有效性的Bug;
■ fix:修复文本组件根据仪表板缩放比例自动缩放文本大小和间隙的Bug;
■ fix:修复仪表板日期组件无法二次编辑的Bug。
-
数据源新增支持Elasticsearch和ClickHouse,DataEase开源数据可视化分析平台v1.3.0发布
2021-10-18 14:17:42新增对组合图、水波图、明细表等视图类型的支持。 -
第四章 数据源配置 davinci 大数据分析平台
2021-11-10 16:08:431 新增数据源 在数据源列表页,点击右上角“+”按钮弹出新增数据源表单 目前支持 JDBC 和 CSV 两种数据源类型;需要注意,CSV 类型数据源也需要指定一个数据库来存储上传的 CSV 文件内容,目前仅支持 mysql ... -
支持Oracle数据源,新增支持消息中心和定时任务中心,DataEase开源数据可视化分析平台v1.1.0发布
2021-08-12 14:39:12视图支持更换数据集。 -
excel切片器_excel筛选技巧:如何做一个动态筛选自动化图表?
2020-10-27 02:40:37所以今天给大家分享一个老板最爱的excel自动化表格,搞定老板,升职加薪,不再是梦!*********最近两个多月,猪肉价格成为很多人关注的热点话题。根据新闻报道,在今年8月的第二周,猪肉价格同比上涨了... -
Tableau 数据源为EXCEL多个sheet时(无关联字段),如何转换数据源?
2017-11-28 09:33:58数据源为同一个EXCEL的不同sheet,且不同sheet间没有相同的关联字段,切换sheet时,原来的图表就会失效,怎样解决? 解决办法:新建数据源,然后替换就可以了 微信公众号:Data Analyst ... -
图表数据实时刷新的实现
2021-05-10 15:18:42图表数据实时刷新 通过美观大气的仪表板展示企业经营数据目前被越来越多的企业管理层重视,数据可视化成为了数据价值发现的重要应用。在展示常规业务数据的同时,更多实时数据展示需求也越来越多,例如电商系统的... -
JimuReport积木报表 — SQL数据源报表制作
2020-11-12 16:15:50一、数据源报表制作、新建数据库表(以student学生表为示例) 1、数据库表结构 2、新增几条数据 二、新建一个报表 1、点击头像进入编辑页面,点击“新建 ->新建报表”即可新建一个空白报表; 2、新建好之后... -
JimuReport积木报表 — API数据源报表制作
2020-11-12 16:23:57一、数据源报表制作、新建数据库表(以student学生表为示例) 1、数据库表结构 2、新增几条数据 3、新增后台api接口(可以使本地,也可以是线上,但必须为json格式) 二、新建API报表 1、点击头像进入个人中心,... -
grafana+TDengine实现物联网采集数据图表显示
2020-12-14 14:12:16至此grafana TDengine数据源就配置好了 在TDengine中创建表并写入几条数据 用客户端连上TDengine 创建数据库 CREATE DATABASE power KEEP 365 DAYS 10 BLOCKS 4 UPDATE 1; 创建一个名为power的库,这个库的数据将... -
axChart_动态可视化图表元件库v1.2.3
2020-12-31 09:05:27axChart,一套支持Axure RP8/9的动态可视化图表元件库。元件的能力,请查看本元件提供的【在线演示】,直观感受axChart的强大配置能力。2020.07.10 v1.2.3更新•新增象形图,包括基础象形图、裁剪象形图和分组象形图... -
【tableau】动态图表和动态排名以及仪表盘
2021-11-16 15:25:01下载了一份新冠疫情的数据 戴师兄-动态仪表盘教程练习数据 - ...只要数据关联了经纬度,tableau会自动把他识别成地图模式。这里本来是一个空白页,双击了一下国家,然后就出来了这个 把新增死亡放到.. -
Excel2007版的常用功能(21):Excel动态图表原理
2019-08-13 11:09:381. 做复选框: 开发者工具--插入--选中复选框 复选框的文字可以更改 ...不选中,单元格显示FALSE,if(那个单元格为TRUE,选中某列数据,否则选中空列) 如:=IF($G$2,$B$2:$B$13,$F$2:$F$13) 必须是... -
云监控---grafana使用mysql数据源创建dashboard--全面解析
2019-12-19 18:43:50grafana的dashboard简介 Grafana是一款采用 go 语言编写的开源应用,主要用于大规模指标数据的可视化展现,是网络架构和应用分析中最流行的时序数据展示工具,目前已经支持绝大部分常用的时序...支持多个数据源; ... -
smartBi数据源连接与业务主题及七大数据集及透视分析与仪表盘四大分析展示经验总结
2021-05-17 00:17:13数据源的数据库包含关系数据库,本地数据库(Java数据源,Excel数据源,主要用于自定义数据结构及上传本地数据文件),多维数据库,高速缓存库(用于抽取数据和优化性能),NoSQL数据库(如MongoD -
vue中 echarts 数据变化 图表不刷新问题
2021-03-26 14:28:08放在watch方法中观察数据源变化进行图表数据设置即可 //观察data的变化 seriesData: { handler (newVal, oldVal) { this.charts = echarts.init(document.getElementById('monitorTendencyChartCont')); if ... -
开源自助BI工具,傻瓜式BI分析,支持多种数据源
2020-09-19 08:55:00一:业务背景与需求梳理公司现在的数据需求主要分为两类:临时需求(业务突然想看看这波活动的效果怎样,数据指标的定义可能随时改,随时加),固化需求(每周要看,每月要看的数据,数据的定义已经非... -
grafana使用MYSQL数据源展示
2020-08-05 15:20:13新增数据源 Grafana支持许多不同的数据源。每个数据源都有一个特定的查询编辑器,该编辑器定制的特性和功能是公开的特定数据来源。 官方支持以下数据源:Graphite,InfluxDB,OpenTSDB,Prometheus,Elasticsearch,... -
Grafana配置数据源,自定义查询语法
2019-08-20 15:38:171.概述--美观、强大的可视化监控指标展示工具 grafana 是一款采用 go 语言编写的开源应用,主要用于大规模指标数据的可视化展现,是网络架构和应用分析中最流行的时序数据展示工具,目前已经支持绝大...每个数据源... -
第三章 BIRT数据源的配置
2018-09-13 09:25:27转载自:https://blog.csdn.net/z1012890225/article/details/37815947 选择数据资源管理器(Data Explorer)。如果使用缺省报表设计透视图,则数据资源管理... -
要让图表随数据源的增加而进行相应的更新,首先可通过Excel组件中的表格工具来实现。使用该工具将工作表中输入的数据创建为表,再通过表数据创建图表,然后只要在表的相邻位置中输入新的数据,这些新增的数据也会...
-
SpringBoot 多租户之多数据源
2020-04-04 01:07:51多租户多数据源开发背景方案实现实现思路实现代码如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个... -
技巧提示 需注意的是,必须在创建了表的相邻行列添加新数据,这样系统才能自动将添加的新数据纳入表的数据源,从而在图表中展示出来。 步骤04 增加行数据。在第7行后添加两个产品在其他日期的销售数据,可看到图表...
-
例2-2:仅对比分析最近10天的销售收入 已知某公司的日销售...在A列和B列数据下方紧邻的空白行中添加几行销售收入数据,其会自动转换为图表的数据源,并可以看到图表同步更新到最近10天的销售收入对比效果,如下图所示。
-
tableau两个不同的图合并_Tableau数据源详解
2021-02-11 12:05:05作者 | CDA数据分析师Tableau内置的连接器可以连接到所有常用的数据源。数据连接器目前可以连接70多种数据源,分为本地连接和服务器连接。 Tableau支持的本地连接包括Excel、txt、csv、json等各类常见的源数据格式,... -
java SSM 多数据源
2016-07-26 12:03:10H 多数据源(支持同时连接无数个数据库,可以不同的模块连接不同数的据库) ------------------------------------------------------------------------------------------------------------------------- ... -
32【源码】数据可视化:基于 Echarts + Python Flask 动态实时大屏 - 监管系统
2022-05-21 10:44:421.动态实时更新数据效果图 2.鼠标右键切换主题 一.确定需求方案 1. 屏幕分辨率 2. 部署方式 二. 整体架构设计 三.编码实现 (基于篇幅及可读性考虑,此处展示部分关键代码) 1. 前端html代码 2.前端JS - ...