使用Java手动创建带有@Transactional方法的Spring @Service实例

3

假设有以下类似的@Service@Repository接口:

@Repository
public interface OrderDao extends JpaRepository<Order, Integer> {

}

public interface OrderService {

    void saveOrder(Order order);

}

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
    }

}

这是一个可用的应用程序部分,一切都被配置为访问单个数据库,而且一切都运行良好。
现在,我希望有可能使用纯Java创建一个具有自动装配OrderDao的独立工作实例的OrderService,并在Java代码中指定jdbcUrl,就像这样:
final int tenantId = 3578;
final String jdbcUrl = "jdbc:mysql://localhost:3306/database_" + tenantId;
OrderService orderService = someMethodWithSpringMagic(appContext, jdbcUrl);

如您所见,我想在现有基于Spring的应用程序中引入“多租户架构”和“每个数据库一个租户”的策略。请注意,我以前使用类似jdbcTemplate的自实现逻辑和正确工作的JDBC事务轻松实现了这一点,因此这是一个非常有效的任务。请注意,我需要相当简单的事务逻辑来启动事务,在该事务范围内对服务方法执行多个请求,然后在异常上提交/回滚。大多数关于Spring的多租户解决方案都建议在xml配置中指定具体的持久性单元和/或使用基于注释的配置,这非常不灵活,因为为了添加新的数据库URL,整个应用程序应该停止,xml配置/注释代码应该进行更改并重新启动应用程序。因此,基本上我正在寻找一段代码,它能够像Spring在从XML配置/注释读取属性之后内部创建服务一样创建@Service。我也在研究使用ProxyBeanFactory来实现这一点,因为Spring使用AOP来创建服务实例(因此我猜想简单的好老的可重用OOP不适用于这里)。Spring是否足够灵活以允许这种相对简单的代码复用?任何提示都将不胜感激,如果我找到了完整的解答,我会在这里发布以供将来的世代使用 :)

2
Hibernate 在多租户方面具有现成的支持,建议在尝试自己实现之前先查看官方文档( http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html )。此外,我们在大约八年前已经编写了通用解决方案,其文档在此处(https://mdeinum.wordpress.com/2007/01/05/one-application-per-client-database/),代码在此处(https://github.com/mdeinum/spring-utils)。 - M. Deinum
@M.Deinum - 非常感谢您快速而有帮助的回复 - 我现在会进行调查并告诉您结果。顺便说一下,我了解到 Hibernate 支持多租户,但不幸的是,该项目使用 JpaRepository,我希望尽可能重用现有的代码库,而不必重写它。 - Yuriy Nakonechnyy
你真的读过手册吗?它被嵌入到Hibernate中并且是透明的... 在做出判断之前,我强烈建议你阅读我给你的手册和链接。 - M. Deinum
@M.Deinum 在提问之前,我查看了Hibernate对多租户的支持,并记得在使用它时遇到了一些障碍。在我之前的评论中,我犯了一个错误,说问题出在JpaRepository上 - 我再次查阅了文档,发现为了实现它,我需要指定MultiTenantConnectionProvider,而其唯一的实现依赖于JNDI,但我不太愿意使用它,因为这会增加额外的复杂性(在我的情况下是不必要的)- 请指教如果我错了。 - Yuriy Nakonechnyy
@M.Deinum 关于你的解决方案 - 我仔细研究了它,发现它真的很棒 - 已经在应用程序中使用了这种方法,一切都像魅力一样工作 :) 我也对Spring中的TargetSource接口非常着迷 - 它确实提供了许多灵活性,有时非常需要 :) 请将您的解决方案发布为答案,我会在测试后点赞并接受它。 - Yuriy Nakonechnyy
2个回答

2
创建一个带注释服务的事务代理并不是一项困难的任务,但我不确定你是否真的需要它。如果要为tenantId选择数据库,我想你只需要集中关注DataSource接口。
例如,使用一个简单的驱动程序管理的数据源:
public class MultitenancyDriverManagerDataSource extends DriverManagerDataSource {

    @Override
    protected Connection getConnectionFromDriverManager(String url,
            Properties props) throws SQLException {

        Integer tenant = MultitenancyContext.getTenantId();

        if (tenant != null)
            url += "_" + tenant;

        return super.getConnectionFromDriverManager(url, props);
    }

}

public class MultitenancyContext {

    private static ThreadLocal<Integer> tenant = new ThreadLocal<Integer>();

    public static Integer getTenantId() {
        return tenant.get();
    }

    public static void setTenatId(Integer value) {
        tenant.set(value);
    }
}

当然,如果您想使用连接池,需要详细说明一下,比如为每个租户使用一个连接池。

感谢您的回答 - 它与评论中的@M.Deinum解决方案非常相似,也应该解决我的问题。我考虑过这样的解决方案,但通常我不太愿意使用共享公共静态实例,这就是为什么我认为可能还有其他依赖于非静态实例的解决方案。然而,看起来要么无法在Spring中实现,要么需要付出很多麻烦 - 请给予建议。 - Yuriy Nakonechnyy
我也喜欢你的解决方案,但发现 M.Deinum 提出的解决方案(指定可交换的数据源为 TargetSource)更符合 "Spring 风格",这就是为什么我接受了他的解决方案 :) - Yuriy Nakonechnyy

2
Hibernate已经内置了对多租户的支持,使用之前请查看官方文档。Hibernate需要一个MultiTenantConnectionProvider和一个CurrentTenantIdentifierResolver,这两个组件都有默认实现,但你也可以编写自己的实现。如果只是一个模式变更,实际上非常简单(在返回连接之前执行查询)。否则,可以维护一个数据源映射表,并从中获取实例,或者创建一个新实例。
大约8年前,我们已经编写了一个通用解决方案,文档在这里,代码在这里。它不仅适用于Hibernate,还可以用于基本上任何需要切换的内容。我们用它来处理DataSource和一些Web相关的事情(例如主题)。

你的解决方案非常好用 - 非常感谢你的帮助! - Yuriy Nakonechnyy
我有另一个相关的问题 - 你尝试使用Tomcat的Http11NioProtocol连接器(或类似的连接器)来提供异步I/O功能吗?我想应该不会有问题,因为我认为Tomcat会透明地处理这个问题,并且在异步读取请求数据后,它会被单个线程处理,但是请给予建议。 - Yuriy Nakonechnyy
不,我们没有遇到过这个问题。那时候还不是问题。所以你在这里就得自己解决了 :) - M. Deinum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接