当前位置: 首页 > news >正文

芝法酱学习笔记(1.3)——SpringBoot+mybatis plus+atomikos实现多数据源事务

一、前言

1.1 业务需求

之前我们在讲解注册和登录的时候,有一个重要的技术点忽略了过去。那就是多数据源的事务问题。
按照我们的业务需求,monitor服务可能涉及同时对监控中心数据库和企业中心数据库进行操作,而我们希望这样的操作在一个事务下。并且,企业中心有多个数据库,我们需要一个自动切库的机制。

1.2 多数据源事务技术选型

多个数据库的切库可以使用AbstractRoutingDataSource作为数据源,但要支持事务却没那么简单。多数据源事务,我们最先想到的就是分布式事务,阿里的seata框架可以很好的解决分布式事务问题。所谓分布式事务,就是指一个事务可能分布在不同的服务器上,但需要各服务器同时完成,然后再提交数据库。如果有哪个服务器失败,则一起回滚。
然而,我们这里的需求,仅仅是单一服务器操作多个数据源。如果因此引入seata,还多了一个事务中心的服务器,无疑增加了运维的成本。所以我们打算使用atomikos实现多数据源的事务。

1.3 atomikos介绍

Atomikos 是一个轻量级的 Java 分布式事务管理器。符合XA 和 JTA(Java Transaction API) 规范。有的帖子说atomikos的性能欠佳,因为会上行锁。但我们扪心自问,我们的业务真的有对同一行数据并发的情况么?
当然,我们后面的大章,可能也会介绍seata的方案。不过当前学习阶段,还是先采用Atomikos吧

二、所遇到的挑战

当我们草草的接入atomikos后,会发现AbstractRoutingDataSource数据源下,在一个Transactional注解内,使用AbstractRoutingDataSource作为动态数据源是无法实现切库的。想要探究其原因,需要翻阅源码。
首先,我们按照网上通行的方式,配置一个AbstractRoutingDataSource。使用单步调试,观察其不可切库的原因。

@Slf4j
@Configuration
public class DataSourceConfig {@Beanpublic DataSource busyDataSource(){MultiDataSource multiDataSource = new MultiDataSource();multiDataSource.init();return multiDataSource;}public class MultiDataSource extends AbstractRoutingDataSource{public static ThreadLocal<String> curKey = new ThreadLocal<>();public void init(){HikariDataSource dataSourceMonitor = DataSourceBuilder.create().type(HikariDataSource.class).driverClassName("com.mysql.cj.jdbc.Driver").url("jdbc:mysql://192.168.0.64:3306/study2024-class007-monitor?useUnicode=true&characterEncoding=utf-8").username("dbMgr").password("???@7").build();HikariDataSource dataSource1 = DataSourceBuilder.create().type(HikariDataSource.class).driverClassName("com.mysql.cj.jdbc.Driver").url("jdbc:mysql://192.168.0.64:3306/study2024-class007-busy001?useUnicode=true&characterEncoding=utf-8").username("dbMgr").password("???@7").build();HikariDataSource dataSource2 = DataSourceBuilder.create().type(HikariDataSource.class).driverClassName("com.mysql.cj.jdbc.Driver").url("jdbc:mysql://192.168.0.64:3306/study2024-class007-busy002?useUnicode=true&characterEncoding=utf-8").username("dbMgr").password("???@7").build();Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("monitor", dataSourceMonitor);targetDataSources.put("busy001", dataSource1);targetDataSources.put("busy002", dataSource2);setTargetDataSources(targetDataSources);setDefaultTargetDataSource(dataSourceMonitor);curKey.set("monitor");}@Overrideprotected Object determineCurrentLookupKey() {return curKey.get();}public static void changeDb(String pKey){curKey.set(pKey);}}
}

我们建一个test_table的表,写一段简单的测试逻辑

@RequiredArgsConstructor
@Service
public class TestCurdServiceImpl implements ITestCurdService {private final IGenMonitorTestDbService mGenMonitorTestDbService;private final IGenBusyTestDbService mGenBusyTestDbService;@Transactional(rollbackFor = Exception.class)@Overridepublic void mpSave(TestEntityDto pTestDto) {GenMonitorTestEntity genMonitorTestEntity = DbDtoEntityUtil.createFromDto(pTestDto,GenMonitorTestEntity.class);GenBusyTestEntity genBusyTestEntity = DbDtoEntityUtil.createFromDto(pTestDto,GenBusyTestEntity.class);genBusyTestEntity.setEnpId(0L);genBusyTestEntity.setEnpCode("sys");genMonitorTestEntity.setEnpId(0L);genMonitorTestEntity.setEnpCode("sys");DataSourceConfig.MultiDataSource.changeDb("monitor");mGenMonitorTestDbService.save(genMonitorTestEntity);DataSourceConfig.MultiDataSource.changeDb("busy001");mGenBusyTestDbService.save(genBusyTestEntity);DataSourceConfig.MultiDataSource.changeDb("busy002");mGenBusyTestDbService.save(genBusyTestEntity);}}

SpringBoot中,通过@EnableTransactionManagement,通过@Import(TransactionManagementConfigurationSelector.class)等一系列操作,最终在ProxyTransactionManagementConfiguration文件配置了一个TransactionInterceptor的bean,其基类TransactionAspectSupport就是Spring对事务AOP实现的核心代码了,拦截我们的Transaction注解下方法的函数是invokeWithinTransaction。再进一步阅读跟踪,发现在DataSourceTransactionManager的doBegin函数中,需要获取datasource的connection,并关闭自动提交。获取的connection,会放到ConnectionHolder里。
而每次执行mybatis的命令,实质上执行的是mybatis包下SimpleExecutor的prepareStatement,每次查找前,都会调用transaction.getConnection()。而这个类被实例化时,他的transaction用的是SpringManagedTransaction。其getConnection代码如下:

public Connection getConnection() throws SQLException {if (this.connection == null) {openConnection();}return this.connection;}

我们可以看到,如果获取过链接了,就不会再获取了。事务doBegin时获取了一次,所以事务注解内的sql执行不会再获取。
而我们发现,SimpleExecutor的实例化是使用Mybatis的Configuration类中的信息,决定使用哪个transactionManager。
那么,我们自己写一个TransactionManager,处理多数据源下的问题,不就解决了么?
另一个问题,我们传统使用bean注解方式创建bean,并不能实现根据配置动态批量的创建bean。然而,我们希望这套代码可以放到公司项目仓库中,被业务代码引用。这时我们就需要使用Spring的容器装配。

三、代码实现

3.1 pom引用

<dependency><groupId>com.atomikos</groupId><artifactId>transactions-spring-boot3-starter</artifactId><version>${atomikos.version}</version>
</dependency>
<atomikos.version>6.0.0</atomikos.version>

3.2 MultiDataSourceTransaction

前边提到,之所以无法切库,是Transaction配置的问题。我们为AbstractRoutingDataSource专门写一个Transaction管理器

@Slf4j
public class MultiDataSourceTransaction implements Transaction {private ZfRoutingDataSource mZfRoutingDataSource;private Map<String,MultiDataSourceConnectionInfo> connectionMap;public MultiDataSourceTransaction(DataSource pDataSource) {if(pDataSource instanceof ZfRoutingDataSource zfRoutingDataSource){mZfRoutingDataSource = zfRoutingDataSource;connectionMap = new ConcurrentHashMap<String,MultiDataSourceConnectionInfo>();}else{throw new ServiceException("传入的DataSource 必须是ZfRoutingDataSource");}}@Overridepublic Connection getConnection() throws SQLException {String curKey = mZfRoutingDataSource.curKey();MultiDataSourceConnectionInfo multiDataSourceConnectionInfo = connectionMap.get(curKey);if(null == multiDataSourceConnectionInfo) {multiDataSourceConnectionInfo = new MultiDataSourceConnectionInfo();DataSource targetDataSource = mZfRoutingDataSource.getTargetDataSource();Connection connection = DataSourceUtils.getConnection(targetDataSource);multiDataSourceConnectionInfo.setDataSource(targetDataSource);multiDataSourceConnectionInfo.setConnection(connection);multiDataSourceConnectionInfo.setAutoCommit(connection.getAutoCommit());multiDataSourceConnectionInfo.setConnectionTransactional(DataSourceUtils.isConnectionTransactional(connection, targetDataSource));connectionMap.put(curKey, multiDataSourceConnectionInfo);}return multiDataSourceConnectionInfo.getConnection();}@Overridepublic void commit() throws SQLException {for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {if(!multiDataSourceConnectionInfo.isConnectionTransactional() && !multiDataSourceConnectionInfo.isAutoCommit()){Connection targetConnection = multiDataSourceConnectionInfo.getConnection();targetConnection.commit();}}}@Overridepublic void rollback() throws SQLException {for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {if(!multiDataSourceConnectionInfo.isConnectionTransactional() && !multiDataSourceConnectionInfo.isAutoCommit()){Connection targetConnection = multiDataSourceConnectionInfo.getConnection();targetConnection.rollback();}}}@Overridepublic void close() throws SQLException {for(MultiDataSourceConnectionInfo multiDataSourceConnectionInfo : connectionMap.values()) {DataSourceUtils.releaseConnection(multiDataSourceConnectionInfo.getConnection(), multiDataSourceConnectionInfo.getDataSource());}connectionMap.clear();}@Overridepublic Integer getTimeout() throws SQLException {var holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(mZfRoutingDataSource);if (holder != null && holder.hasTimeout()) {return holder.getTimeToLiveInSeconds();}return null;}
}
public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {@Overridepublic Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {return new MultiDataSourceTransaction(dataSource);}
}

3.3 mapper的代码结构

首先,我们明确一下需求。我们的数据库分2种,一种是监控中心的库,只有一个数据库实例。而企业中心,却有多个实例。
我们在开发中,可以把监控中心的库的mapper和企业中心的不放在一个包中,分别配置:
在这里插入图片描述
这样,我们可以把监控中心配置为单数据源,企业中心配置为多数据源。

@MapperScans(value = {@MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.monitor.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_monitor"),@MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.busy.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_busy"),
}
)

3.4 RoutingDataSource

我们针对多数据源,考虑有些项目可能有多组不同的多数据源。比如常见的订单中心,优惠券中心,商品定义中心。写个自己的memo。

public class ZfRoutingDataSource extends AbstractRoutingDataSource {ThreadLocal<String> curDbKey = new ThreadLocal<String>();@Overrideprotected Object determineCurrentLookupKey() {return curDbKey.get();}public String curKey(){return curDbKey.get();}public void set(String pKey) {curDbKey.set(pKey);}public void clear() {curDbKey.remove();}public DataSource getTargetDataSource() {return determineTargetDataSource();}
}
public class RoutingDataSourceMemo {private Map<String,ZfRoutingDataSource> mRoutingDataSource;public RoutingDataSourceMemo() {mRoutingDataSource = new ConcurrentHashMap<String,ZfRoutingDataSource>();}public ZfRoutingDataSource createRoutingDataSource(String pKey, Map<Object, Object> pDataSourceMap, DataSource pPrimaryDataSource) {ZfRoutingDataSource zfRoutingDataSource = new ZfRoutingDataSource();zfRoutingDataSource.setTargetDataSources(pDataSourceMap);zfRoutingDataSource.setDefaultTargetDataSource(pPrimaryDataSource);zfRoutingDataSource.initialize();mRoutingDataSource.put(pKey, zfRoutingDataSource);return zfRoutingDataSource;}public ZfRoutingDataSource get(String pKey) {return Optional.<ZfRoutingDataSource>ofNullable(mRoutingDataSource.get(pKey)).orElseThrow(()->new ServiceException("没有找到key为"+pKey+"的数据源"));}
}
@Configuration
public class RoutingDataSourceMemoConfigure {@BeanRoutingDataSourceMemo routingDataSourceMemo(){return new RoutingDataSourceMemo();}
}

3.5 bean装配

3.5.1 常见接口的介绍

ImportBeanDefinitionRegistrar

一般该接口配合@Import使用。
该接口一般用于导入bean,其有2个接口。

void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator);
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);

该接口可以读取类上的注解,并依据注解的配置信息,批量注册创建bean的PostProcessor的定义。
该接口允许类继承以下4个接口,用于获取Spring容器的一些关键对象

  • EnvironmentAware
  • BeanFactoryAware
  • BeanClassLoaderAware
  • ResourceLoaderAware
BeanDefinitionRegistryPostProcessor

该接口一般用于批量注册实际的bean,其下也有2个接口

	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;@Overridedefault void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

第一个接口用于向容器注册bean的定义,第二个接口可以直接向容器注册bean的实例

FactoryBean

.该接口用于延迟化实例化bean。由于许多bean的实例化需要依赖其他bean的创建,那么干脆在该bean被第一次使用到时进行加载。实际上,SpringBoot对Bean的生命周期管理,是基于FactoryBean而非具体的Bean。
该接口的核心接口为:

T getObject() throws Exception;

3.5.2 MultiDataSourceConfigurerRegister

现在,我们开始编写本章需求的代码。这个Register用于向容器注册一个批量注册数据源和SessionFactory的PostProcessor

@Slf4j
public class MultiDataSourceConfigurerRegister implements ImportBeanDefinitionRegistrar,EnvironmentAware, ResourceLoaderAware, BeanFactoryAware {private Environment mEnvironment;private ResourceLoader mResourceLoader;private BeanFactory mBeanFactory;public MultiDataSourceConfigurerRegister() {}@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MultiDataSourceConfigurer.class);builder.addConstructorArgValue(mBeanFactory);builder.addConstructorArgValue(mResourceLoader);builder.addConstructorArgValue(mEnvironment);builder.addConstructorArgReference("routingDataSourceMemo");registry.registerBeanDefinition(MultiDataSourceConfigurer.class.getName(),builder.getBeanDefinition());}@Overridepublic void setEnvironment(Environment pEnvironment) {mEnvironment = pEnvironment;}@Overridepublic void setResourceLoader(ResourceLoader pResourceLoader) {mResourceLoader = pResourceLoader;}@Overridepublic void setBeanFactory(BeanFactory pBeanFactory) throws BeansException {mBeanFactory = pBeanFactory;}
}

3.5.3 MultiDataSourceConfigurerRegister

该类是注册Datasource和SessionFactory的FactoryBean的类

@Slf4j
public class MultiDataSourceConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware {private BeanFactory mBeanFactory;private ResourceLoader mResourceLoader;private Environment mEnvironment;private ApplicationContext mApplicationContext;static final String DATASOURCE_PREFIX = "datasource_";static final String SQL_SESSION_FACTORY_PREFIX = "sqlSessionFactory_";static final String SQL_SESSION_TEMPLATE_PREFIX = "sqlSessionTemplate_";RoutingDataSourceMemo mRoutingDataSourceMemo;public MultiDataSourceConfigurer(BeanFactory pBeanFactory,ResourceLoader pResourceLoader,Environment pEnvironment,RoutingDataSourceMemo pRoutingDataSourceMemo) {mBeanFactory = pBeanFactory;mResourceLoader = pResourceLoader;mEnvironment = pEnvironment;mRoutingDataSourceMemo = pRoutingDataSourceMemo;}@Overridepublic void afterPropertiesSet() throws Exception {}@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {String profile = mEnvironment.getProperty("spring.profiles.active");String url = mEnvironment.getProperty("spring.datasource.url");String userName = mEnvironment.getProperty("spring.datasource.username");String password = mEnvironment.getProperty("spring.datasource.password");SingleConnectionDataSource singleConnectionDataSource = new SingleConnectionDataSource(url, userName,password,false);JdbcTemplate jdbcTemplate = new JdbcTemplate(singleConnectionDataSource);List<Map<String,Object>> res =  jdbcTemplate.queryForList("select * from sys_db where profile = ?",profile);List<SysDbEntity> sysDbEntityList = res.stream().map(m-> BeanUtil.toBean(m,SysDbEntity.class)).collect(Collectors.toList());Map<String,List<SysDbEntity>> sysDbEntityListMap = sysDbEntityList.stream().collect(Collectors.groupingBy(SysDbEntity::getDatasourceGroup));for(Map.Entry<String,List<SysDbEntity>> entry : sysDbEntityListMap.entrySet()){String datasourceGroup = entry.getKey();List<SysDbEntity> list = entry.getValue();SysDbEntity first = list.get(0);switch (first.getDatasourceType()){case 0 ->{AbstractBeanDefinition singleDataSourceBeanDef = genSingleDataSourceFactorBean(first);registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,singleDataSourceBeanDef);AbstractBeanDefinition singleSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.SINGLE,datasourceGroup);registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,singleSqlSessionFactorBean);}case 1 ->{AbstractBeanDefinition multiDataSourceBeanDef = genMultiDataSourceFactorBean(datasourceGroup,list,mRoutingDataSourceMemo);registry.registerBeanDefinition(DATASOURCE_PREFIX+datasourceGroup,multiDataSourceBeanDef);AbstractBeanDefinition multiSqlSessionFactorBean = genSqlSessionFactorBean(EDataSourceType.MULTI,datasourceGroup);registry.registerBeanDefinition(SQL_SESSION_FACTORY_PREFIX+datasourceGroup,multiSqlSessionFactorBean);}}}}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {mApplicationContext = applicationContext;}public AbstractBeanDefinition genSingleDataSourceFactorBean(SysDbEntity pSysDbEntity){BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SingleDataSourceFactorBean.class);builder.addConstructorArgValue(pSysDbEntity);return builder.getBeanDefinition();}public AbstractBeanDefinition genMultiDataSourceFactorBean(String pKey,List<SysDbEntity> pSysDbEntityList,RoutingDataSourceMemo pRoutingDataSourceMemo){BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MultiDataSourceFactorBean.class);builder.addConstructorArgValue(pKey);builder.addConstructorArgValue(pSysDbEntityList);builder.addConstructorArgValue(pRoutingDataSourceMemo);return builder.getBeanDefinition();}public AbstractBeanDefinition genSqlSessionFactorBean(EDataSourceType pDataSourceType, String pDataSourceName){BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactorBean.class);builder.addConstructorArgValue(pDataSourceType);builder.addConstructorArgReference(DATASOURCE_PREFIX + pDataSourceName);ObjectProvider<MybatisPlusProperties> mybatisPlusPropertiesProvider = mBeanFactory.getBeanProvider(MybatisPlusProperties.class);builder.addConstructorArgValue(mybatisPlusPropertiesProvider);builder.addConstructorArgValue(mResourceLoader);builder.addConstructorArgValue(mApplicationContext);ObjectProvider<Interceptor> interceptorsProvider = mBeanFactory.getBeanProvider(Interceptor.class);builder.addConstructorArgValue(interceptorsProvider);ObjectProvider<TypeHandler> typeHandlerProvider = mBeanFactory.getBeanProvider(TypeHandler.class);builder.addConstructorArgValue(typeHandlerProvider);ObjectProvider<LanguageDriver> languageDriverProvider = mBeanFactory.getBeanProvider(LanguageDriver.class);builder.addConstructorArgValue(languageDriverProvider);ObjectProvider<DatabaseIdProvider> databaseIdProviderProvider = mBeanFactory.getBeanProvider(DatabaseIdProvider.class);builder.addConstructorArgValue(databaseIdProviderProvider);ResolvableType configurationCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, ConfigurationCustomizer.class);ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizerListProvider = mBeanFactory.getBeanProvider(configurationCustomizerTargetType);builder.addConstructorArgValue(configurationCustomizerListProvider);ResolvableType sqlSessionFactoryBeanCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, SqlSessionFactoryBeanCustomizer.class);ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizerListProvider = mBeanFactory.getBeanProvider(sqlSessionFactoryBeanCustomizerTargetType);builder.addConstructorArgValue(sqlSessionFactoryBeanCustomizerListProvider);ResolvableType mybatisPlusPropertiesCustomizerTargetType = ResolvableType.forClassWithGenerics(List.class, MybatisPlusPropertiesCustomizer.class);ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider = mBeanFactory.getBeanProvider(mybatisPlusPropertiesCustomizerTargetType);builder.addConstructorArgValue(mybatisPlusPropertiesCustomizerProvider);// 显式添加配置类的依赖//builder.addDependsOn("mybatisPlusInterceptor");return builder.getBeanDefinition();}}

3.5.4 Dadasouce的FactorBean

public class SingleDataSourceFactorBean implements FactoryBean<DataSource> {private final SysDbEntity mSysDbEntity;public SingleDataSourceFactorBean(SysDbEntity pSysDbEntity) {mSysDbEntity = pSysDbEntity;}@Overridepublic DataSource getObject() throws Exception {MysqlXADataSource dataSource = DataSourceBuilder.create().type(MysqlXADataSource.class).url(mSysDbEntity.getDbUrl()).username(mSysDbEntity.getDbUser()).password(mSysDbEntity.getDbPasswd()).build();AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();atomikosDataSourceBean.setUniqueResourceName(mSysDbEntity.getDbKey());atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.cj.jdbc.XADataSource");atomikosDataSourceBean.setXaDataSource(dataSource);Optional.ofNullable(mSysDbEntity.getMinIdle()).ifPresent(atomikosDataSourceBean::setMinPoolSize);Optional.ofNullable(mSysDbEntity.getMaxPoolSize()).ifPresent(atomikosDataSourceBean::setMaxPoolSize);return atomikosDataSourceBean;}@Overridepublic Class<?> getObjectType() {return DataSource.class;}
}
public class MultiDataSourceFactorBean implements FactoryBean<DataSource>  {private final String mKey;private final List<SysDbEntity> mSysDbEntityList;private final RoutingDataSourceMemo mRoutingDataSourceMemo;public MultiDataSourceFactorBean(String pKey, List<SysDbEntity> pSysDbEntityList, RoutingDataSourceMemo pRoutingDataSourceMemo) {mKey = pKey;mSysDbEntityList = pSysDbEntityList;mRoutingDataSourceMemo = pRoutingDataSourceMemo;}@Overridepublic DataSource getObject() throws Exception {Map<Object, Object> dataSourceMap = new HashMap<>();DataSource primaryDataSource = null;for (SysDbEntity sysDbEntity : mSysDbEntityList) {MysqlXADataSource dataSource = DataSourceBuilder.create().type(MysqlXADataSource.class).url(sysDbEntity.getDbUrl()).username(sysDbEntity.getDbUser()).password(sysDbEntity.getDbPasswd()).build();AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();atomikosDataSourceBean.setUniqueResourceName(sysDbEntity.getDbKey());atomikosDataSourceBean.setXaDataSourceClassName("com.mysql.cj.jdbc.XADataSource");atomikosDataSourceBean.setXaDataSource(dataSource);Optional.ofNullable(sysDbEntity.getMinIdle()).ifPresent(atomikosDataSourceBean::setMinPoolSize);Optional.ofNullable(sysDbEntity.getMaxPoolSize()).ifPresent(atomikosDataSourceBean::setMaxPoolSize);dataSourceMap.put(sysDbEntity.getDbKey(), atomikosDataSourceBean);if(sysDbEntity.getPrimary()){primaryDataSource = atomikosDataSourceBean;}}ZfRoutingDataSource dataSourceRouter = mRoutingDataSourceMemo.createRoutingDataSource(mKey,dataSourceMap,primaryDataSource);return dataSourceRouter;}@Overridepublic Class<?> getObjectType() {return DataSource.class;}
}

3.5.5 Dadasouce的FactorBean

该类需要重点说一下,mybatisplus对mybatis的拓展功能,是依赖SqlSessionFactory实现的。所以单纯的new一个MybatisSqlSessionFactoryBean ,而后getObject,将会使所有mybatis-plus的功能无法使用。我下面的这段代码,是copy并修改mybatis-plus的MybatisPlusAutoConfiguration。

public class SqlSessionFactorBean implements FactoryBean<SqlSessionFactory> {private final DataSource mDataSource;private final ObjectProvider<MybatisPlusProperties> mPropertiesProvider;private final ResourceLoader mResourceLoader;private final ApplicationContext mApplicationContext;private final EDataSourceType mDataSourceType;/* mp用到的一些变量*/private final ObjectProvider<Interceptor> mInterceptorsProvider;private final ObjectProvider<TypeHandler> mTypeHandlersProvider;private final ObjectProvider<LanguageDriver> mLanguageDriversProvider;private final ObjectProvider<DatabaseIdProvider> mDatabaseIdProviderProvider;private final ObjectProvider<List<ConfigurationCustomizer>> mConfigurationCustomizersProvider;private final ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> mSqlSessionFactoryBeanCustomizersProvider;private final ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mMybatisPlusPropertiesCustomizersProvider;private Interceptor[] mInterceptors;private TypeHandler[] mTypeHandlers;private LanguageDriver[] mLanguageDrivers;private DatabaseIdProvider mDatabaseIdProvider;private List<ConfigurationCustomizer> mConfigurationCustomizers;private List<SqlSessionFactoryBeanCustomizer> mSqlSessionFactoryBeanCustomizers;private List<MybatisPlusPropertiesCustomizer> mMybatisPlusPropertiesCustomizers;private MybatisPlusProperties mMybatisPlusProperties;public SqlSessionFactorBean(EDataSourceType pDataSourceType,DataSource pDataSource,ObjectProvider<MybatisPlusProperties> propertiesProvider,ResourceLoader resourceLoader,ApplicationContext applicationContext,ObjectProvider<Interceptor> interceptorsProvider,ObjectProvider<TypeHandler> typeHandlersProvider,ObjectProvider<LanguageDriver> languageDriversProvider,ObjectProvider<DatabaseIdProvider> databaseIdProvider,ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers,ObjectProvider<List<MybatisPlusPropertiesCustomizer>> mybatisPlusPropertiesCustomizerProvider) {mDataSourceType = pDataSourceType;mDataSource = pDataSource;this.mPropertiesProvider = propertiesProvider;this.mInterceptorsProvider = interceptorsProvider;this.mTypeHandlersProvider = typeHandlersProvider;this.mLanguageDriversProvider = languageDriversProvider;this.mResourceLoader = resourceLoader;this.mDatabaseIdProviderProvider = databaseIdProvider;this.mConfigurationCustomizersProvider = configurationCustomizersProvider;this.mSqlSessionFactoryBeanCustomizersProvider = sqlSessionFactoryBeanCustomizers;this.mMybatisPlusPropertiesCustomizersProvider = mybatisPlusPropertiesCustomizerProvider;this.mApplicationContext = applicationContext;}@Overridepublic SqlSessionFactory getObject() throws Exception {getMpBeans();switch (mDataSourceType){case MULTI -> {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();MultiDataSourceTransactionFactory transactionFactory = new MultiDataSourceTransactionFactory();sqlSessionFactory(bean,mDataSource);bean.setTransactionFactory(transactionFactory);return bean.getObject();}case SINGLE -> {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();sqlSessionFactory(bean,mDataSource);return bean.getObject();}}return null;}@Overridepublic Class<?> getObjectType() {return null;}protected void getMpBeans(){mInterceptors = mInterceptorsProvider.stream().toArray(Interceptor[]::new);mTypeHandlers = mTypeHandlersProvider.stream().toArray(TypeHandler[]::new);mLanguageDrivers = mLanguageDriversProvider.stream().toArray(LanguageDriver[]::new);mDatabaseIdProvider = mDatabaseIdProviderProvider.getIfAvailable();mConfigurationCustomizers = mConfigurationCustomizersProvider.getIfAvailable();mSqlSessionFactoryBeanCustomizers = mSqlSessionFactoryBeanCustomizersProvider.getIfAvailable();mMybatisPlusPropertiesCustomizers = mMybatisPlusPropertiesCustomizersProvider.getIfAvailable();mMybatisPlusProperties = mPropertiesProvider.getIfAvailable();}public MybatisSqlSessionFactoryBean sqlSessionFactory(MybatisSqlSessionFactoryBean pMybatisSqlSessionFactoryBean ,DataSource dataSource) throws Exception {MybatisSqlSessionFactoryBean factory = pMybatisSqlSessionFactoryBean;factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);if (StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {factory.setConfigLocation(this.mResourceLoader.getResource(this.mMybatisPlusProperties.getConfigLocation()));}applyConfiguration(factory);if (this.mMybatisPlusProperties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.mMybatisPlusProperties.getConfigurationProperties());}if (!ObjectUtils.isEmpty(this.mInterceptors)) {factory.setPlugins(this.mInterceptors);}if (this.mDatabaseIdProvider != null) {factory.setDatabaseIdProvider(this.mDatabaseIdProvider);}if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeAliasesPackage())) {factory.setTypeAliasesPackage(this.mMybatisPlusProperties.getTypeAliasesPackage());}if (this.mMybatisPlusProperties.getTypeAliasesSuperType() != null) {factory.setTypeAliasesSuperType(this.mMybatisPlusProperties.getTypeAliasesSuperType());}if (StringUtils.hasLength(this.mMybatisPlusProperties.getTypeHandlersPackage())) {factory.setTypeHandlersPackage(this.mMybatisPlusProperties.getTypeHandlersPackage());}if (!ObjectUtils.isEmpty(this.mTypeHandlers)) {factory.setTypeHandlers(this.mTypeHandlers);}if (!ObjectUtils.isEmpty(this.mMybatisPlusProperties.resolveMapperLocations())) {factory.setMapperLocations(this.mMybatisPlusProperties.resolveMapperLocations());}Class<? extends LanguageDriver> defaultLanguageDriver = this.mMybatisPlusProperties.getDefaultScriptingLanguageDriver();if (!ObjectUtils.isEmpty(this.mLanguageDrivers)) {factory.setScriptingLanguageDrivers(this.mLanguageDrivers);}Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);applySqlSessionFactoryBeanCustomizers(factory);GlobalConfig globalConfig = this.mMybatisPlusProperties.getGlobalConfig();this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);this.getBeanThen(AnnotationHandler.class, globalConfig::setAnnotationHandler);this.getBeanThen(PostInitTableInfoHandler.class, globalConfig::setPostInitTableInfoHandler);this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);factory.setGlobalConfig(globalConfig);return factory;}private <T> void getBeanThen(Class<T> clazz, Consumer<T> consumer) {if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {consumer.accept(this.mApplicationContext.getBean(clazz));}}private <T> void getBeansThen(Class<T> clazz, Consumer<List<T>> consumer) {if (this.mApplicationContext.getBeanNamesForType(clazz, false, false).length > 0) {final Map<String, T> beansOfType = this.mApplicationContext.getBeansOfType(clazz);List<T> clazzList = new ArrayList<>();beansOfType.forEach((k, v) -> clazzList.add(v));consumer.accept(clazzList);}}private void applyConfiguration(MybatisSqlSessionFactoryBean factory) {MybatisPlusProperties.CoreConfiguration coreConfiguration = this.mMybatisPlusProperties.getConfiguration();MybatisConfiguration configuration = null;if (coreConfiguration != null || !StringUtils.hasText(this.mMybatisPlusProperties.getConfigLocation())) {configuration = new MybatisConfiguration();}if (configuration != null && coreConfiguration != null) {coreConfiguration.applyTo(configuration);}if (configuration != null && !CollectionUtils.isEmpty(this.mConfigurationCustomizers)) {for (ConfigurationCustomizer customizer : this.mConfigurationCustomizers) {customizer.customize(configuration);}}factory.setConfiguration(configuration);}private void applySqlSessionFactoryBeanCustomizers(MybatisSqlSessionFactoryBean factory) {if (!CollectionUtils.isEmpty(this.mSqlSessionFactoryBeanCustomizers)) {for (SqlSessionFactoryBeanCustomizer customizer : this.mSqlSessionFactoryBeanCustomizers) {customizer.customize(factory);}}}
}

四、代码展示

在主类上,添加如下注解,就可以使用多数据源事务了

@EnableZfMultiDataSource
@MapperScans(value = {@MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.monitor.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_monitor"),@MapperScan(value = {"indi.zhifa.study2024.nbr.monitor.gen.busy.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory_busy"),
}
)

具体代码请移步我的 码云

相关文章:

芝法酱学习笔记(1.3)——SpringBoot+mybatis plus+atomikos实现多数据源事务

一、前言 1.1 业务需求 之前我们在讲解注册和登录的时候&#xff0c;有一个重要的技术点忽略了过去。那就是多数据源的事务问题。 按照我们的业务需求&#xff0c;monitor服务可能涉及同时对监控中心数据库和企业中心数据库进行操作&#xff0c;而我们希望这样的操作在一个事…...

图像处理插件:让小程序焕发视觉新生的秘密武器

在小程序开发中&#xff0c;图像处理是一个重要的环节&#xff0c;它涉及到图片的加载、显示、裁剪、压缩等多个方面。为了简化这一复杂过程&#xff0c;开发者通常会使用图像处理插件。这些插件不仅提供了丰富的图像处理功能&#xff0c;还封装了底层的图像操作逻辑&#xff0…...

力扣刷题TOP101: 27.BM34 判断是不是二叉搜索树

目录&#xff1a; 目的 思路 复杂度 记忆秘诀 python代码 目的&#xff1a; 给定一个二叉树根节点&#xff0c;请判断这棵树是不是二叉搜索树。 二叉搜索树满足每个节点的左子树上的所有节点均小于当前节点且右子树上的所有节点均大于当前节点。 思路 什么是二叉搜索树&am…...

Linux图形化工具推荐

1、MobaXterm MobaXterm Xserver with SSH, telnet, RDP, VNC and X11 - DownloadFree X server for Windows with tabbed SSH terminal, telnet, RDP, VNC and X11-forwarding - Downloadhttps://mobaxterm.mobatek.net/download.html 2、FinalShell FinalShell SSH工具,服…...

蓝队基础:企业网络安全架构与防御策略

声明 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;此文章为对视频内容稍加整理发布&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他…...

数据结构:栈

什么是栈&#xff1a; 栈是一种特殊的线性表&#xff0c;仅能在线性表的一端操作&#xff0c;栈顶允许操作&#xff0c;栈底不允许操作。 栈的特点是&#xff1a;先进后出&#xff0c;或者说是后进先出&#xff0c;从栈顶放入元素的操作叫入栈&#xff0c;取出元素叫出栈。 栈…...

tcp_recvmsg 函数

tcp_recvmsg 函数是 Linux 内核 TCP 栈的一部分,它主要用于处理从 TCP socket 接收数据的过程。这个函数的主要任务是从 TCP 接收队列中提取数据,并将这些数据拷贝到用户空间提供的缓冲区中。 以下是 tcp_recvmsg 函数的一般工作流程和功能解释: 函数签名和参数 int tcp_re…...

《数据结构》(应用题)

历年真题&#xff08;09~24&#xff09; 2009 最短路径&#xff08;Dijkstra青春版&#xff09; 【2009统考真题】带权图&#xff08;权值非负&#xff0c;表示边连接的两顶点间的距离&#xff09;的最短路径问题是找出从初始顶点到目标顶点之间的一条最短路径。假设从初始顶点…...

阿里内部正式开源“Spring Cloud Alibaba (全彩小册)”

年轻的毕业生们满怀希望与忐忑&#xff0c;去寻找、竞争一个工作机会。已经在职的开发同学&#xff0c;也想通过社会招聘或者内推的时机争取到更好的待遇、更大的平台。 然而&#xff0c;面试人群众多&#xff0c;技术市场却相对冷淡&#xff0c;面试的同学们不得不面临着 1 个…...

LeetCode题练习与总结:根据字符出现频率排序--451

一、题目描述 给定一个字符串 s &#xff0c;根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。 返回 已排序的字符串 。如果有多个答案&#xff0c;返回其中任何一个。 示例 1: 输入: s "tree" 输出: "eert" …...

Excel VBA学习系列汇总20241205

整理几年工作中&#xff0c;实用VBA代码&#xff0c;绝对干货&#xff01; 方便自己查询&#xff0c;方便大家学习&#xff0c; 有缘人可复制使用&#xff0c;记得分享给大家免费学习哦&#xff01; 序历史文章1新学期开始&#xff0c;如何新学期开始&#xff0c;如何按成绩名次…...

给el-table表头添加icon图标,以及鼠标移入icon时显示el-tooltip提示内容

在你的代码中&#xff0c;你已经正确地使用了 el-tooltip 组件来实现鼠标划过加号时显示提示信息。el-tooltip 组件的 content 属性设置了提示信息的内容&#xff0c;placement 属性设置了提示信息的位置。 你需要确保 el-tooltip 组件的 content 属性和 placement 属性设置正…...

基于LLM智能问答系统【阿里云:天池比赛】

流程&#xff1a; 1、分别识别问题及提供的资料文件中的公司名实体&#xff0c;有公司名的走语义检索&#xff0c;无公司名的走结构化召回 2、结构化召回&#xff1a;Qwen根据问题生成sql&#xff0c;执行sql获取结果数值&#xff0c;把结果数值与问题给到Qwen生成最终结果 …...

k8s-Informer概要解析(2)

Client-go 主要用在 k8s 控制器中 什么是 k8s Informer Informer 负责与 kubernetes APIServer 进行 Watch 操作&#xff0c;Watch 的资源&#xff0c;可以是 kubernetes 内置资源对象&#xff0c;也可以 CRD。 Informer 是一个带有本地缓存以及索引机制的核心工具包&#x…...

Leetcode 3376. Minimum Time to Break Locks I

Leetcode 3376. Minimum Time to Break Locks I 1. 解题思路2. 代码实现 题目链接&#xff1a;3376. Minimum Time to Break Locks I 1. 解题思路 这一题我最开始的思路走的是贪婪算法的路子&#xff0c;优先走X的增长&#xff0c;不过很不幸失败了&#xff0c;后面还是暴力…...

介绍8款开源网络安全产品

01 HFish蜜罐 HFish是一款开源的蜜罐系统&#xff0c;用于模拟各种网络服务和应用&#xff0c;以吸引潜在的黑客攻击。它能够记录攻击尝试并收集攻击者的信息&#xff0c;从而帮助网络管理员识别潜在的威胁。HFish支持多种协议和服务&#xff0c;包括HTTP、FTP、SSH等&#…...

vue2面试题|[2024-12-5]

开题答辩终于结束了&#xff0c;又要开始我的前端面试学习啦&#xff01;&#xff01;&#xff01; 1.v-model双向绑定原理 class Vue{constructor(options){this.$options optionsthis.$watchEvent {}if(typeof options.beforeCreate function){options.beforeCreate.bind…...

共筑数字安全防线,2024开源和软件安全沙龙即将启幕

随着数字化转型进程的加快以及开源代码的广泛应用&#xff0c;开源凭借平等、开放、协作、共享的优秀创作模式&#xff0c;逐渐成为推动数字技术创新、加速传统行业转型升级的重要模式。但随着软件供应链日趋复杂多元&#xff0c;使得其安全风险不断加剧&#xff0c;针对软件供…...

目标跟踪领域经典论文解析

亲爱的小伙伴们&#x1f618;&#xff0c;在求知的漫漫旅途中&#xff0c;若你对深度学习的奥秘、JAVA 、PYTHON与SAP 的奇妙世界&#xff0c;亦或是读研论文的撰写攻略有所探寻&#x1f9d0;&#xff0c;那不妨给我一个小小的关注吧&#x1f970;。我会精心筹备&#xff0c;在…...

SQL DQL数据查询语言(后续)

SQL DQL数据查询语言&#xff08;后续&#xff09; 1.子查询 在查询语句中的WHERE条件子句中&#xff0c;又嵌套了另外一个查询语句在返回列中嵌套一个查询 where条件中嵌套 要求&#xff1a;查询课程为《高等数学-2》且分数不小于80分的学生的学号和姓名select a.StudentNo,a…...

Gitee配置SSH公钥

采用SSH协议同步Git仓库代码的好处就是高效。在配置好SSH公钥后&#xff0c;不需要每次操作都要输入用户名和密码&#xff08;主要针对命令行来说&#xff09;。 以我个人项目为例。 生成 SSH 公钥 1. 通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen -t ed25519…...

机器学习——感知机模型

文章目录 前言1.感知机模型介绍1.1基本概念1.2数学表达1.3几何解释1.4优缺点 2.二分类应用2.1应用介绍2.2准备数据集2.2.1环境检查2.2.2数据集介绍2.2.3获取数据2.2.4划分数据集 2.3可视化训练集2.4训练过程2.4.1首轮梯度下降2.4.2多轮梯度下降 2.5可视化分类结果2.6在验证集验…...

如何选择安全、可验证的技术?

澳大利亚信号局的澳大利亚网络安全中心 (ASD 的 ACSC) 发布了一份指导文件&#xff0c;题为《选择安全和可验证的技术》&#xff0c;旨在帮助组织在采购软件&#xff08;专有或开源&#xff09;、硬件&#xff08;例如物联网设备&#xff09;和云服务&#xff08;SaaS、MSP 服务…...

STL库中list的使用与迭代器的实现

STL库中list的使用与迭代器的实现 1.使用list中的部分函数assignspliceremoveuniquemeger 2.list的部分功能实现&#xff08;重点&#xff09;框架迭代器的实现 1.使用list中的部分函数 assign 功能一&#xff1a;当前链表的节点全部销毁&#xff0c;替换成迭代区间的值 功能二…...

android 常用三方框架

说实话&#xff0c; 我是比较讨厌三方框架的&#xff0c; 比如一个eventbus 底层逻辑就是个观察者模式&#xff0c;当然他的场景涵盖的比较丰富&#xff0c; 单从 单一原则来说&#xff0c; 还是一个简单的观察者模式就能解决问题&#xff0c; 何必要添加那么多文件到我们的项目…...

Browser.js断点续传上传

通过断点续传上传的方式将文件上传到OSS前&#xff0c;您可以指定断点记录点。上传过程中&#xff0c;如果出现网络异常或程序崩溃导致文件上传失败时&#xff0c;将从断点记录处继续上传未上传完成的部分。 attention&#xff1a; 1、 当您使用webpack或browserify等打包工具…...

Java项目实战II基于微信小程序的无中介租房系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着城市化进程的加速&#xff0c;租房市场日益繁荣&a…...

了解Cocoa Touch框架与主要组件

Cocoa Touch框架详解及其主要组件 一、Cocoa Touch框架概述 Cocoa Touch框架是苹果公司为iOS应用程序开发提供的一套完整的框架&#xff0c;它基于Cocoa框架&#xff0c;并专为触控设备如iPhone、iPad等设计。这套框架不仅包含了构建图形用户界面&#xff08;GUI&#xff09;…...

ISO45001职业健康安全管理体系涵盖了丰富的内容

范围与术语 适用范围&#xff1a;明确规定了该标准适用于任何有愿望建立、实施和保持职业健康安全管理体系的组织&#xff0c;旨在使组织能够通过管理体系的有效运行&#xff0c;预防和控制职业健康安全风险&#xff0c;持续改进职业健康安全绩效。术语定义&#xff1a;对职业…...

Spring Boot 整合 Druid 并开启监控

文章目录 1. 引言2. 添加依赖3. 配置数据源4. 开启监控功能5. 自定义 Druid 配置&#xff08;可选&#xff09;6. 访问监控页面7. 注意事项8. 总结 Druid 是一个由阿里巴巴开源的高性能数据库连接池&#xff0c;它不仅提供了高效的连接管理功能&#xff0c;还自带了强大的监控和…...

【JAVA高级篇教学】第一篇:Springboot对接通义千问大模型

博主今天打算讲解下Java如何对接阿里云的通义千问大模型&#xff0c;可以自己玩玩ai问答之类的&#xff01; 目录 一、发展历程 二、API-KEY的获取与配置 三、引用SDK 四、文本模型 1.代码 2.返回数据 3.官方代码案例 五、通义千问VL 1.计量计费 六、查看API-KEY调用额…...

【Windows 同时安装 MySQL5 和 MySQL8 - 详细图文教程】

卸载 MySQL 参考文章&#xff1a; 完美解决Mysql彻底删除并重装_怎么找到mysql并卸载-CSDN博客使用命令卸载mysql_卸载mysql服务命令-CSDN博客 先管理员方式打开 cmd &#xff0c;切换到 MySQL 安装目录的 bin 文件夹下&#xff0c;执行如下命令&#xff0c;删除 MySQL 服务 my…...

Next.js 系统性教学:深入理解缓存与数据优化策略

更多有关Next.js教程&#xff0c;请查阅&#xff1a; 【目录】Next.js 独立开发系列教程-CSDN博客 目录 前言 1. 缓存的基本概念 1.1 缓存的作用 1.2 Next.js 中的缓存策略 2. Next.js 的缓存机制 2.1 请求记忆化&#xff08;Request Memoization&#xff09; 2.1.1 什…...

JAVA数据结构

1.数组 (Array): 固定大小的容器,用于存储相同类型的元素,数组在内存中是连续存储的,支持通过索引快 速访问元素。 int[] numbers = new int[10]; numbers[0] = 1;2.Java Collections Framework (JCF) JCF提供了一组接口和类用于管理和操作集合(如列表,集合,…...

力扣第96题 不同的二叉搜索树

力扣第96题 - 不同的二叉搜索树 题目描述 给定一个整数 n&#xff0c;求以 1 到 n 为节点组成的所有 不同的二叉搜索树&#xff08;BST&#xff09; 的个数。 题目分析 二叉搜索树的性质 对于一个二叉搜索树&#xff0c;以 i 为根节点&#xff1a; 左子树的节点值来自 [1, i…...

在Ubuntu上使用IntelliJ IDEA:开启你的Java开发之旅!

你好&#xff0c;年轻的学徒&#xff01;&#x1f9d1;‍&#x1f4bb; 是时候踏上进入Java开发世界的史诗之旅了&#xff0c;我们的得力助手将是强大的IntelliJ IDEA。准备好了吗&#xff1f;出发吧&#xff01; 在我们开始之前&#xff0c;我们需要下载这个工具。但是&#…...

【C语言】18. 自定义类型:结构体类型

文章目录 前言&#xff1a;一、结构体类型的声明1、结构体回顾1&#xff09;结构的声明2&#xff09;结构体变量的创建和初始化 2、结构的特殊声明3、结构的⾃引⽤ 二、结构体变量的创建和初始化1、对⻬规则2、为什么存在内存对⻬?3、修改默认对⻬数 三、结构成员访问操作符1、…...

智能租赁管理系统助力规范化住房租赁市场提升用户体验

内容概要 在当今的住房租赁市场中&#xff0c;智能租赁管理系统应运而生&#xff0c;为房东和租客带来了前所未有的便利。这套系统就像一位全能助手&#xff0c;将租赁信息、监管机制以及在线签约功能集成在一起&#xff0c;让整个过程变得流畅而高效。换句话说&#xff0c;您…...

ERROR: KeeperErrorCode = NoNode for /hbase/master

原因分析 通过上面的情景模拟&#xff0c;我们可以看到报错的原因在于zookeeper中出现问题&#xff0c;可能是zookeeper中的/hbase/master被删除&#xff0c;或者是在hbase集群启动之后重新安装了zookeeper&#xff0c;导致zookeeper中的/hbase/master节点数据异常。 1. 停止…...

springboot第84集:Java进阶之路, Netty

# kafka-map文件夹 cd /usr/local/kafka-map # 根据需求自行修改配置 vi application.yml # 启动 java -jar kafka-map.jar byte minByte -128; byte maxByte 127; 用于表示一个 8 位&#xff08;1 字节&#xff09;有符号整数。它的值范围是 -128&#xff08;-2^7&#xff0…...

DevOps持续集成

DevOps流程 第一步安装git 关闭防火墙 systemctl stop firewalld cd /usr/loacl vim docker-compose.yml docker search gitlab 拉取gitlab镜像 2.33GB docker pull gitlab/gitlab-ce:latestvim docker-compose.yml修改docker-compose.yml version: 3.1 services:gitlab:i…...

sql server log文件

确定 SQL Server 实例中具有大量 VDF 的数据库 SELECT [name], COUNT(l.database_id) AS vlf_count FROM sys.databases AS s CROSS APPLY sys.dm_db_log_info(s.database_id) AS l GROUP BY [name] HAVING COUNT(l.database_id) > 100; 在收缩日志文件之前确定事务日志中…...

pip install报错 Missing dependencies for SOCKS support的正确解决办法:离线安装pysocks

今天准备开发python项目的时候&#xff0c;发现在pip install 的时候报错了&#xff0c;提示&#xff1a;Missing dependencies for SOCKS support&#xff0c;查遍csdn所有的回答都统一是只需要执行&#xff1a; unset all_proxy unset ALL_PROXY 然后再执行 pip install p…...

嵌入式学习(15)-stm32通用GPIO模拟串口发送数据

一、概述 在项目开发中可能会遇到串口不够用的情况这时候可以用通过GPIO来模拟串口的通信方式。 二、协议格式 按照1位起始位8位数据位1位停止位的方式去编写发送端的程序。起始位拉低一个波特率的时间&#xff1b;发送8位数据&#xff1b;拉高一个波特率的时间。 三、代码 …...

AKE 安全模型:CK, CK+, eCK

参考文献&#xff1a; [BCK98] Mihir Bellare, Ran Canetti, Hugo Krawczyk. A Modular Approach to the Design and Analysis of Authentication and Key Exchange Protocols (Extended Abstract). STOC 1998: 419-428.[CK01] Ran Canetti, Hugo Krawczyk. Analysis of Key-E…...

【Linux】通过crond服务设置定时执行shell脚本,实际执行时间却延迟了8小时

一、问题描述 通过使用crond服务设置定时任务&#xff0c;在每天凌晨的2:00执行脚本&#xff0c;但检查结果时发现&#xff0c;实际执行时间却在上午10点。 检查shell脚本执行结果发现&#xff0c;实际执行脚本时间在上午10:00&#xff0c;延迟了8小时。 检查系统时间&#xf…...

什么是云原生数据库 PolarDB?

云原生数据库 PolarDB 是阿里云推出的一款高性能、兼容性强、弹性灵活的关系型数据库产品。它基于云原生架构设计&#xff0c;结合分布式存储和计算分离的技术优势&#xff0c;为用户提供强大的计算能力、卓越的可靠性以及高性价比的数据库解决方案。PolarDB 适合各种业务场景&…...

(6)JS-Clipper2之ClipperOffset

1. 描述 ClipperOffset类封装了对打开路径和关闭路径进行偏移(膨胀/收缩)的过程。 这个类取代了现在已弃用的OffsetPaths函数&#xff0c;该函数不太灵活。可以使用不同的偏移量(增量)多次调用Execute方法&#xff0c;而不必重新分配路径。现在可以在一次操作中对开放和封闭路…...

基于51单片机64位病床呼叫系统设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机病床呼叫系统设计( proteus仿真程序设计报告原理图讲解视频&#xff09; 仿真图proteus7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0095 1. 主要功能&#xff1a; 基于51单片机的病床呼叫系统proteus仿…...

工业智能网关如何为企业实现智能制造赋能?

在数字化转型的浪潮中&#xff0c;工业智能网关作为连接物理世界与数字世界的桥梁&#xff0c;正逐步成为智能制造领域的核心组件。本文将通过一个实际使用案例&#xff0c;深入剖析工业智能网关如何助力企业实现生产流程的优化、数据的高效采集与分析&#xff0c;以及智能化决…...