定义 Spring AOP 后无法切换数据库

2024-01-24

我正在研究多租户架构,我们必须处理数据库的动态创建和切换。

我面临的问题是,我在 AOP 级别编写了切换数据库的整个代码,然后在控制器或服务级别我无法进行另一个切换。

AOP类

/**
 * The type Api util.
 */
@Aspect
@Component
@Order(2)
public class APIUtil {
    private static final Logger log = LoggerFactory.getLogger(APIUtil.class);
    private final SchoolMasterService schoolMasterService;

    /**
     * Instantiates a new Api util.
     *
     * @param schoolMasterService the school master service
     * @param helperService
     */
    public APIUtil(SchoolMasterService schoolMasterService) {
        this.schoolMasterService = schoolMasterService;
    }

    /**
     * Around controller methods object.
     *
     * @param proceedingJoinPoint the proceeding join point
     * @return the object
     * @throws Throwable the throwable
     */
    @Around("execution(* com.example.board.controller.*.*(..))")
    public Object aroundControllerMethods(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("All schools loaded!");
        TenantContext.setCurrentTenant(DEFAULT_TENANT_ID);
        schoolMasterService.findAllMasters();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String tenantID = request.getHeader(XTENANTID).trim();
        return filterByHeader(tenantID, proceedingJoinPoint);
    }

   private Object filterByHeader(String tenantID, ProceedingJoinPoint joinPoint) throws Throwable {
        SchoolMaster schoolMaster = schoolMasterService.findBySchoolId(Long.parseLong(tenantID));
        log.info(format("Current school is %s", schoolMaster.getDataSourceKey()));
        TenantContext.setCurrentTenant(schoolMaster.getDataSourceKey());
        return joinPoint.proceed();
    }
}

CurrentTenantIdentifierResolver Impl 类

@Component
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public String resolveCurrentTenantIdentifier() {
        String currentDataSourceKey = TenantContext.getCurrentTenant();
        if (Objects.isNull(currentDataSourceKey)) {
            currentDataSourceKey = DEFAULT_TENANT_ID;
        }
        logger.debug("currentDataSourceKey {}", currentDataSourceKey);
        return currentDataSourceKey;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

MultiTenantConnectionProvider Impl 类

@Component(MultiTenantConnectionProviderImpl.BEAN_ID)
public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
    /**
     * The constant BEAN_ID.
     */
    public static final String BEAN_ID = "multiTenantConnectionProvider";
    private static final long serialVersionUID = 7395318315512114572L;

    @Autowired
    private TenantDataSource tenantDataSource;

    private Logger log;

    /**
     * Instantiates a new Multi tenant connection provider.
     */
    public MultiTenantConnectionProviderImpl() {
        log = LoggerFactory.getLogger(getClass());
    }

    @Override
    protected DataSource selectAnyDataSource() {
        log.debug("selectAnyDataSource , returning dafault tenantid");
        return tenantDataSource.getDataSource(DEFAULT_TENANT_ID);
    }

    @Override
    protected DataSource selectDataSource(String tenantIdentifier) {
        log.debug("selected Datasource {} ", tenantIdentifier);
        return tenantDataSource.getDataSource(tenantIdentifier);
    }
}

租户数据源类

@Component
public class TenantDataSource {
    private final Map<Object, Object> tenantDataSourcesMap = new HashMap<>();
    private final Logger log = LoggerFactory.getLogger(TenantDataSource.class);

    private final DataSourceProperties dataSourceProperties;
    private final DataSource dataSource;
    private final JdbcTemplate jdbcTemplateObject;

    /**
     * Instantiates a new Tenant data source.
     *
     * @param properties the properties
     * @param source     the source
     * @param object     the object
     */
    public TenantDataSource(DataSourceProperties properties, DataSource source, JdbcTemplate object) {
        this.dataSourceProperties = properties;
        this.dataSource = source;
        this.jdbcTemplateObject = object;
    }

    /**
     * Add default datasource to map.
     */
    @PostConstruct
    void addDefaultDatasourceToMap() {
        tenantDataSourcesMap.put(DEFAULT_TENANT_ID, dataSource);
    }

    /**
     * Gets data source.
     *
     * @param dataSourceName the data source name
     * @return the data source
     */
    public DataSource getDataSource(String dataSourceName) {
        DataSource currentDatasource = null;
        log.debug("getDataSource().dataSourceName {}", dataSourceName);
        if (tenantDataSourcesMap.containsKey(dataSourceName)) {
            currentDatasource = (DataSource) tenantDataSourcesMap.get(dataSourceName);
        }
        return currentDatasource;
    }

    /**
     * Load tenant boolean.
     *
     * @param tenantDatasource the tenant datasource
     * @return the boolean
     */
    public boolean loadTenant(SchoolMaster tenantDatasource) {
        try {
            if (!verifyPort(tenantDatasource))
                return false;
            DataSource temp = createDataSource(tenantDatasource);
            boolean result = verifyConnection(temp);
            if (result) {
                tenantDataSourcesMap.putIfAbsent(tenantDatasource.getDataSourceKey(), temp);
            }
            return result;
        } catch (Exception h) {
            return false;
        }
    }

    /**
     * Load all tenants.
     *
     * @param tenantDatasourcesList the tenant datasources list
     */
    public void loadAllTenants(List<SchoolMaster> tenantDatasourcesList) {
        tenantDatasourcesList.forEach(tenant -> tenantDataSourcesMap.putIfAbsent(tenant.getDataSourceKey(), createDataSource(tenant)));
    }

    /**
     * Create data source data source.
     *
     * @param tenantDatasource the tenant datasource
     * @return the data source
     */
    public DataSource createDataSource(SchoolMaster tenantDatasource) {
        HikariDataSource hikariDataSource = null;
        if (Objects.nonNull(tenantDatasource)) {
            String url = JDBCMYSQL + tenantDatasource.getSchoolIP().trim() + ":" + tenantDatasource.getDataSourcePort().trim() + SLASH + tenantDatasource.getDataSourceKey().trim() + "?createDatabaseIfNotExist=true&useSSL=true";
            hikariDataSource = (HikariDataSource) DataSourceBuilder.create()
                    .driverClassName(dataSourceProperties.getDriverClassName())
                    .username(tenantDatasource.getDataSourceUserName()).password(tenantDatasource.getDataSourcePassword())
                    .url(url)
                    .build();
            setConnectionPooling(hikariDataSource);
        }
        return hikariDataSource;
    }

    /**
     * Create schema.
     *
     * @param dataSourceName the data source name
     * @throws SQLException the sql exception
     */
    public void createSchema(String dataSourceName) throws SQLException {
        if (tenantDataSourcesMap.containsKey(dataSourceName)) {
            jdbcTemplateObject.execute(CREATE_SCHEMA + " " + dataSourceName);
            jdbcTemplateObject.execute(USE_SCHEMA + " " + dataSourceName);
            DataSource currentDataSource = (DataSource) tenantDataSourcesMap.get(dataSourceName);
            ClassPathResource resource = new ClassPathResource("dbscripts/schema.sql");
            try (Connection connection = currentDataSource.getConnection()) {
                ScriptUtils.executeSqlScript(connection, new EncodedResource(resource, "UTF-8"));
            }
            jdbcTemplateObject.execute(USE_SCHEMA + " " + DEFAULT_TENANT_ID);
        }
    }

    /**
     * Drop schema.
     *
     * @param dataSourceName the data source name
     */
    public void dropSchema(String dataSourceName) {
        if (tenantDataSourcesMap.containsKey(dataSourceName)) {
            DataSource currentDataSource = (DataSource) tenantDataSourcesMap.get(dataSourceName);
            JdbcTemplate template = new JdbcTemplate(currentDataSource);
            template.execute(DROP_SCHEMA + " " + dataSourceName);
        }
    }

    /**
     * Sets connection pooling.
     *
     * @param hikariDataSource the hikari data source
     */
    private void setConnectionPooling(HikariDataSource hikariDataSource) {
        hikariDataSource.setMinimumIdle(2);
        hikariDataSource.setMaximumPoolSize(5);
        hikariDataSource.setIdleTimeout(100000);
        hikariDataSource.setMaxLifetime(3000000);
        hikariDataSource.setConnectionTimeout(200000);
        hikariDataSource.setLeakDetectionThreshold(2100);
        hikariDataSource.setConnectionTestQuery("SELECT 1 FROM DUAL");
        hikariDataSource.setAutoCommit(false);
    }


    /**
     * Verify connection boolean.
     *
     * @param currentDatasource the current datasource
     * @return the boolean
     */
    private boolean verifyConnection(DataSource currentDatasource) {
        try (Connection ignored = currentDatasource.getConnection()) {
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * Verify port boolean.
     *
     * @param tenantDataSource the tenant data source
     * @return the boolean
     */
    private boolean verifyPort(SchoolMaster tenantDataSource) {
        return tenantDataSource.getDataSourcePort().trim().chars().allMatch(Character::isDigit);
    }
}

租户上下文类

public final class TenantContext {
    private static final ThreadLocal<String> currentTenant = ThreadLocal.withInitial(() -> DEFAULT_TENANT_ID);

    private TenantContext() {
    }

    /**
     * Gets current tenant.
     *
     * @return the current tenant
     */
    public static String getCurrentTenant() {
        return currentTenant.get();
    }

    /**
     * Sets current tenant.
     *
     * @param tenant the tenant
     */
    public static void setCurrentTenant(String tenant) {
        currentTenant.set(tenant);
    }

    /**
     * Clear.
     */
    public static void clear() {
        currentTenant.remove();
    }
}

在控制器/服务级别,我无法使用以下命令切换数据库TenantContext.setCurrentTenant(String identifier)但我可以在 AOP 上做同样的事情。

这是什么原因呢?有什么办法可以解决这个问题吗?

我将非常感谢任何帮助。

我添加了服务代码的示例

我需要切换数据库两次,这是不可能的。

 @Transactional(readOnly = true)
    @Override
    public List<SyllabusListingResponseDto> getSyllabusByBoardId(Long rackId, Long languageId) {
        logger.info("getSyllabusByBoardId Method called in BoardManagementServiceImpl");
        ResourceRackModel resourceRackModel = resourceRackService.getByRackIdAndStatus(rackId, ACTIVE_STATUS);
        if (Objects.nonNull(resourceRackModel)) {
            TenantContext.setCurrentTenant(DEFAULT_TENANT_ID);
            List<Long> rackIds = resourceRackService.findAllRackIdsByBoardId(rackId);
            rackIds.add(rackId);
            ResourceRackModel boardModel = resourceRackModel;
            if (!boardModel.getParentPath().isEmpty()) {
                String[] ids = resourceRackModel.getParentPath().split(",", 2);
                boardModel = resourceRackService.getByRackIdAndStatus(Long.parseLong(ids[INT_ZERO]), ACTIVE_STATUS);
            }
            TenantContext.setCurrentTenant("S_" + 1);
            BoardVersionModel activeVersionModel = boardVersionRepository.findByBoardIdAndStatusAndVersionStatus(boardModel.getRackId(), ACTIVE_STATUS, ACTIVE_STATUS);
            ContentCountDto contentCountDto = new ContentCountDto().setStatus(true).setRackIds(rackIds).setActiveBoardVersionId(activeVersionModel.getVersionId().toString());
            ResponseModel responseModel = nemrSTCManagementClient.findContentCount(Common.getTenantId(), contentCountDto).getBody();
            if (Objects.nonNull(responseModel)) {
                Map<String, List<String>> lookup = (Map<String, List<String>>) responseModel.getObject();
                String languageCode = languageMasterService.findByIdAndStatus(languageId, ACTIVE_STATUS).getLanguageCode();
                String defaultLanguageCode = languageMasterService.findByIdAndStatus(resourceRackModel.getDefaultLanguageId(), ACTIVE_STATUS).getLanguageCode();
                List<ResourceRackModel> resourceRackModels = resourceRackService.findByParentIdAndStatus(rackId, ACTIVE_STATUS);
                if (resourceRackModels.isEmpty()) {
                    return Collections.emptyList();
                }
                Map<Integer, String> rackTypes = new HashMap<>();
                return getResult(languageId, lookup, new SyllabusListingResponseDto(), boardModel, languageCode, defaultLanguageCode, resourceRackModels, rackTypes);
            }
        }
        return Collections.emptyList();
    }


  1. 由于我没有使用过MultiTenantConnectionProvider所以不确定在什么时候datasource由框架选择。

  2. 但我非常怀疑这是由interceptor自动创建者@Transactional,并且框架从不读取TenantContext方法内部。即一种事务方法的一个数据源。 因此,如果我的怀疑属实,可能值得确定。

    • 您可以在以下位置放置断点:
        logger.info("getSyllabusByBoardId Method called in BoardManagement...");
    
    • 然后当断点存在时清除控制台日志。

    • 然后让该方法执行并查看当您单步执行服务方法行时是否打印以下日志。

        log.debug("selectAnyDataSource , returning dafault tenantid");
    
       log.debug("selected Datasource {} ", tenantIdentifier);
    
  3. 如果我在步骤 2 中的怀疑是正确的,那么您必须删除@Transactional注释或将方法分成两部分并设置正确的TenantContext.setCurrentTenant在调用服务方法之前,对控制器中的每个方法进行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

定义 Spring AOP 后无法切换数据库 的相关文章

  • Java new Date() 打印

    刚刚学习 Java 我知道这可能听起来很愚蠢 但我不得不问 System out print new Date 我知道参数中的任何内容都会转换为字符串 最终值是 new Date 返回对 Date 对象的引用 那么它是如何打印这个的呢 Mo
  • 为什么 i++ 不是原子的?

    Why is i Java 中不是原子的 为了更深入地了解 Java 我尝试计算线程中循环的执行频率 所以我用了一个 private static int total 0 在主课中 我有两个线程 主题 1 打印System out prin
  • Play框架运行应用程序问题

    每当我尝试运行使用以下命令创建的新 Web 应用程序时 我都会收到以下错误Play http www playframework org Error occurred during initialization of VM Could no
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 加速代码 - 3D 数组

    我正在尝试提高我编写的一些代码的速度 我想知道从 3d 整数数组访问数据的效率如何 我有一个数组 int cube new int 10 10 10 我用价值观填充其中 然后我访问这些值数千次 我想知道 由于理论上所有 3d 数组都存储在内
  • 我可以使用 HSQLDB 进行 junit 测试克隆 mySQL 数据库吗

    我正在开发一个 spring webflow 项目 我想我可以使用 HSQLDB 而不是 mysql 进行 junit 测试吗 如何将我的 mysql 数据库克隆到 HSQLDB 如果您使用 spring 3 1 或更高版本 您可以使用 s
  • 无法解析插件 Java Spring

    我正在使用 IntelliJ IDEA 并且我尝试通过 maven 安装依赖项 但它给了我这些错误 Cannot resolve plugin org apache maven plugins maven clean plugin 3 0
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 为什么HashMap不能保证map的顺序随着时间的推移保持不变

    我在这里阅读有关 Hashmap 和 Hashtable 之间的区别 http javarevisited blogspot sg 2010 10 difference Between hashmap and html http javar
  • Java执行器服务线程池[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果我使用 Executor 框架在
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • 编译器抱怨“缺少返回语句”,即使不可能达到缺少返回语句的条件

    在下面的方法中 编译器抱怨缺少退货声明即使该方法只有一条路径 并且它包含一个return陈述 抑制错误需要另一个return陈述 public int foo if true return 5 鉴于Java编译器可以识别无限循环 https
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 使用 JMF 创建 RTP 流时出现问题

    我正处于一个项目的早期阶段 需要使用 RTP 广播DataStream创建自MediaLocation 我正在遵循一些示例代码 该代码目前在rptManager initalize localAddress 出现错误 无法打开本地数据端口
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O
  • Spring Boot @ConfigurationProperties 不从环境中检索属性

    我正在使用 Spring Boot 1 2 1 并尝试创建一个 ConfigurationProperties带有验证的bean 如下所示 package com sampleapp import java net URL import j

随机推荐

  • 如何在 PySpark ML 中创建自定义 SQLTransformer 来透视数据

    我有一个类似于以下结构的数据框 Prepare training data training spark createDataFrame 990011 1001 01 Salary 1000 0 0 990011 1002 02 POS P
  • 显示 Rails Carrierwave URL,而不暴露整个路径

    我正在制作一个付费下载的网络应用程序 并且我遵循了 Carrierwave 的指南保护上传 https github com carrierwaveuploader carrierwave wiki how to secure upload
  • 如何更新进度条的进度以显示复制文件的进度

    我想要一个进度条来指示正在复制的文件如何通过组合以下代码行来更新进度条以显示复制文件的进度 这是我的进度条代码 FXML private void startbtnaction ActionEvent event startbtn setD
  • ASP.NET 5 beta 7 的共享类库参考

    在我们当前的环境中 我们有一些共享的公共库 C 类库 NET 4 5 1 csproj 项目 它们被 ASP NET 和控制台应用程序引用 我们正在考虑将 1 个 Web 项目升级到 ASP NET 5 以开始测试即将到来的一些新更改 由于
  • Flexbox 在 Safari 中添加 1px 左边距

    我在 Safari 向 Flexbox 行中第一个元素的左侧添加 1px 边距 间隙时遇到问题 我在下面附上了该问题的图片 弹性框 CSS 是 equal height display webkit box OLD iOS 6 Safari
  • 如何使用 apache poi 从公式单元格读取单元格值

    我正在尝试从 Excel 文件中读取所有数据 该文件也有一些公式单元格 但我不知道哪个单元格是公式单元格 无论单元格的类型如何 我如何读取单元格中的所有值 我的代码看起来像这样 FormulaEvaluator evaluator wb g
  • 如何命名 es6 类(针对 React 组件)

    这是 ES6 问题的一部分 React 问题的一部分 我正在尝试使用命名空间组件 https facebook github io react docs jsx in depth html namespaced components在 Re
  • Android 搜索图标未显示在操作栏上

    当我在 Android 上学习本教程时遇到了一个问题 正如主题中提到的 我的搜索图标没有显示在操作栏上 我在那一步遇到了问题 http developer android com training basics actionbar addi
  • 如果向量包含值,C++ 代码将不会运行

    我正在使用 VS Code 和 Vim Windows 操作系统 MSYS2 MingW GCC G V 11 2 0 编译器 如果我的代码包含向量 则运行代码时不会有输出 编译或运行代码时没有收到错误 警告 include
  • Python 中的过滤对象数组

    我正在使用 Python 来挖掘一个相当大的项目并挖掘有关它的信息 我能够创建一个数组ProjectFiles 但是我很难弄清楚如何过滤它 class ProjectFile def init self filename str numbe
  • github页面中的图像和相关链接

    我创建了一个用户的 github 页面 现在 假设我在存储库的根目录中有一个图像文件 位置为Images Emoticons Cool png 我尝试将该图像插入到我的主图像中Index html file 我写的 img src 线上和线
  • 在 GitHub 中,有没有办法查看所有分支上的所有(最近)提交?

    在 GitHub 中 有没有办法查看所有分支上的所有最近提交 最好按照时间倒序排列 也许我是个窥探者 但我希望能够看到我的开发人员最近做了什么 至少在对 github 存储库的提交方面 到目前为止 我见过的最接近的是网络图 这当然非常有用
  • 如何让unix脚本每15秒运行一次?

    我见过一些解决方案 包括监视和简单地在后台运行循环 和休眠 脚本 但没有一个是理想的 我有一个脚本需要每 15 秒运行一次 并且由于 cron 不支持秒 所以我不得不解决其他问题 在 UNIX 上每 15 秒运行一次脚本最稳健 最有效的方法
  • 使用加速缩放 Ycbcr (420f) 时的伪像

    我找不到任何有关如何调整 Ycbcr biplanar 大小的文档或示例 根据 Apple 的说法 Ycbcr biplanar 应该是您应该在 iOS 上使用的主要格式 我尝试像这样调整两个飞机的大小 resize luma vImage
  • 无法加载时区?

    我正在尝试将时区加载到我的 MySql 实例 在 Ubuntu 上运行 中 以便我可以使用 CONVERT TZ 函数 但是当我运行以下命令时 mysql tzinfo to sql usr share zoneinfo mysql u r
  • 为什么 .PHONY 在这种情况下不起作用?

    我有一个复杂的 makefile 似乎每次调用它时都会重新链接我的库和可执行文件 我能够将问题缩小到一个简单的 makefile 1 all prog 2 3 PHONY prog 4 prog prog exe 5 6 prog exe
  • Haskell 中的教堂数字

    我正在尝试使用以下定义在 haskell 中打印教堂数字 0 fx x 1 fx f x 哈斯克尔代码 c0 f x gt x c1 f x gt f x 当我在 haskell 控制台中输入它时 我收到一条错误消息 test gt c1
  • 错误Emacs slime:eval-buffer:符号的函数定义为void:define-slime-contrib

    我在短时间内使用 emacs 和 lisp 使用 slime 这个工作正常 但是今天当我运行它时出现以下消息 eval buffer 符号的函数定义为空 define slime contrib 这意味着我的 slime contrib 文
  • Visual Studio:如何管理项目之间共享的代码

    这可能之前已经发布过 但我不确定要查找什么搜索词 快速解释 我有一些项目之间共享的代码 该代码本身仍在进行中 问题是 每当我需要更新此代码时 我不想更新 3 次 这将成为一场噩梦 有没有办法将其添加到项目中 而不将其复制到项目文件夹中 即我
  • 定义 Spring AOP 后无法切换数据库

    我正在研究多租户架构 我们必须处理数据库的动态创建和切换 我面临的问题是 我在 AOP 级别编写了切换数据库的整个代码 然后在控制器或服务级别我无法进行另一个切换 AOP类 The type Api util Aspect Componen