如何在Spring容器外使用Spring Data JPA?

50

我正在尝试手动连接Spring Data JPA对象,以便生成DAO代理(也称为Repositories)-而不使用Spring bean容器。

不可避免地,我会被问及为什么要这样做:因为我们的项目已经在使用Google Guice(并且在UI上使用Gin和GWT),我们不想维护另一个IoC容器配置,或者拉入所有相关的依赖项。我知道我们可能可以使用Guice的 SpringIntegration ,但这将是最后的办法。

似乎一切都可以手动连接起来,但由于没有很好的文档,我非常困难。

根据Spring Data用户指南,可以独立使用repository factories standalone。不幸的是,示例显示了一个抽象类RepositoryFactorySupport。经过一些搜索,我成功找到了JpaRepositoryFactory

JpaRepositoryFactory实际上运作得相当不错,但它不会自动创建事务。必须手动管理事务,否则将不会将任何内容持久化到数据库中:

entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();

问题在于 @Transactional 注释不会自动使用,需要 TransactionInterceptor 的帮助。

值得庆幸的是,JpaRepositoryFactory 可以接受回调函数,在返回生成的 Repository 代理之前添加更多的 AOP 建议:

final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(emf.createEntityManager());

factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new AnnotationTransactionAttributeSource()));
    }
});

事情不是很顺利。在代码调试器中,TransactionInterceptor 确实创建了一个事务 - 但是它关联的是错误的 EntityManager。Spring 通过查看当前执行线程来管理活动的 EntityManagerTransactionInterceptor 这样做,并发现线程上没有绑定到活动的 EntityManager,因此决定创建一个新的。

然而,这个新的 EntityManager 不是传递给 JpaRepositoryFactory 构造函数并且被创建的实例,构造函数需要一个 EntityManager。问题是,如何使 TransactionInterceptorJpaRepositoryFactory 使用相同的 EntityManager

更新:

在撰写本文时,我找到了解决问题的方法,但仍然可能不是最理想的解决方案。我将把这个解决方案作为单独的答案发表。如果有更好的使用 Spring Data JPA 的方式,请告诉我。


1
(+1)写得好(并且有趣)的问题 - Ralph
2个回答

26
JpaRepositoryFactory 的设计原则及其与 Spring 集成的 bean 是基于以下思想:

我们假设您在受控的 JPA 运行时环境中运行应用程序,而无需关心具体是哪个。

这就是为什么我们依赖注入的 EntityManager 而不是 EntityManagerFactory。按照定义,EntityManager 不是线程安全的。因此,如果直接使用 EntityManagerFactory,我们需要重写所有资源管理代码,以便像 Spring 或 EJB 一样为您提供受控运行时环境。

为了与 Spring 事务管理集成,我们使用 Spring 的 SharedEntityManagerCreator,它实际上执行了您手动实现的事务资源绑定魔法。因此,您可能希望使用该方法从您的 EntityManagerFactory 创建 EntityManager 实例。如果您想直接在存储库 bean 中激活事务性(例如,如果没有已经激活的事务,则调用 repo.save(…) 将创建一个事务),请查看 Spring Data Commons 中的 TransactionalRepositoryProxyPostProcessor 实现。它实际上在直接使用 Spring Data 存储库时(例如针对 repo.save(…))激活事务,并略微自定义事务配置查找以优先使用接口而不是实现类,以允许存储库接口覆盖在 SimpleJpaRepository 中定义的事务配置。


我会研究一下您的建议。谢谢。 - codemaven
总之,你的建议非常有效。实际上,SharedEntityManagerCreator是最有帮助的部分。谢谢。 - codemaven
2
@codemaven 如果您能使用围绕SharedEntityManagerCreator的新解决方案更新您的答案,那将非常棒。 - quantum
请注意,将类似的说明添加到http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.create-instances可能会很有帮助。如果涉及到这么多内容,那么注释可能有点简洁了。 - Visionary Software Solutions

14

我通过手动绑定EntityManagerEntityManagerFactory到执行线程,然后使用JpaRepositoryFactory创建仓库来解决了这个问题。这可以通过使用TransactionSynchronizationManager.bindResource方法来实现:

emf = Persistence.createEntityManagerFactory("com.foo.model", properties);
em = emf.createEntityManager();

// Create your transaction manager and RespositoryFactory
final JpaTransactionManager xactManager = new JpaTransactionManager(emf);
final JpaRepositoryFactory factory = new JpaRepositoryFactory(em);

// Make sure calls to the repository instance are intercepted for annotated transactions
factory.addRepositoryProxyPostProcessor(new RepositoryProxyPostProcessor() {
    @Override
    public void postProcess(ProxyFactory factory) {
        factory.addAdvice(new TransactionInterceptor(xactManager, new MatchAlwaysTransactionAttributeSource()));
    }
});

// Create your repository proxy instance
FooRepository repository = factory.getRepository(FooRepository.class);

// Bind the same EntityManger used to create the Repository to the thread
TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));

try{
    repository.save(someInstance); // Done in a transaction using 1 EntityManger
} finally {
    // Make sure to unbind when done with the repository instance
    TransactionSynchronizationManager.unbindResource(getEntityManagerFactory());
}

然而,似乎有更好的方法。使用 EnitiyManager 而不是 EntityManagerFactory 来设计 RepositoryFactory 似乎很奇怪。我期望它首先查看线程是否绑定了 EntityManger,然后要么创建一个新的并将其绑定,要么使用现有的。

基本上,我想要注入仓储代理,并且期望每次调用时它们会内部创建一个新的 EntityManager,以使调用线程安全。


1
抱歉耽搁了,StackOverflow 让我等了8个小时才能发布问题和回答,因为我是新用户。 - codemaven
你好!现在API合同已更改。RepositoryProxyPostProcessor中的void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation);。这个RepositoryInformation是什么,我该如何获取它?(((( - Capacytron

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