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

cod*_*ven 46 spring jpa java-ee spring-data spring-data-jpa

我正在尝试手动连接Spring Data JPA对象,以便我可以生成DAO代理(也称为存储库) - 而无需使用Spring bean容器.

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

似乎所有东西都可用于手动连接对象,但由于它没有很好地记录,我现在遇到了困难.

根据Spring Data用户指南,可以单独使用存储库工厂.不幸的是,该示例显示了RepositoryFactorySupport哪个是抽象类.经过一番搜索,我找到了JpaRepositoryFactory

JpaRepositoryFactory实际上工作得很好,除了它不会自动创建交易.必须手动管理事务,否则任何内容都不会持久保存到数据库:

entityManager.getTransaction().begin();
repositoryInstance.save(someJpaObject);
entityManager.getTransaction().commit();
Run Code Online (Sandbox Code Playgroud)

问题原来是@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()));
    }
});
Run Code Online (Sandbox Code Playgroud)

这是事情进展不顺利的地方.单步执行代码中的调试器,TransactionInterceptor确实创建了一个事务 - 但错误EntityManager.Spring EntityManager通过查看当前正在执行的线程来管理活动.这样TransactionInterceptor做并看到EntityManager线程没有活动绑定,并决定创建一个新线程.

但是,这个new EntityManager与创建并传递给JpaRepositoryFactory构造函数的实例不同,后者需要一个EntityManager.问题是,我如何制作TransactionInterceptorJpaRepositoryFactory使用相同的EntityManager

更新:

在写这篇文章时,我发现了如何解决问题,但它仍然可能不是理想的解决方案.我将此解决方案作为单独的答案发布.我很高兴听到有关使用Spring Data JPA独立的更好方法的建议,而不是我如何解决它.

Oli*_*ohm 25

JpaRepositoryFactory根据Spring集成JpaRepositoryFactorybean 的设计背后的一般原则如下:

我们假设您在托管 JPA运行时环境中运行应用程序,而不是关心哪一个.

这就是我们依赖注入EntityManager而不是注入的原因EntityManagerFactory.根据定义,EntityManager它不是线程安全的.因此,如果EntityManagerFactory直接处理,我们将不得不重写所有资源管理代码,托管运行时环境(就像Spring或EJB)将为您提供.

为了与Spring事务管理集成,我们使用Spring SharedEntityManagerCreator,它实际上是你手动实现的事务资源绑定魔法.所以你可能想用那个来创建EntityManager你的实例EntityManagerFactory.如果要直接激活存储库bean中的事务性(以便repo.save(…)在没有活动的情况下调用例如创建事务),请查看TransactionalRepositoryProxyPostProcessorSpring Data Commons 中的实现.它实际上在直接使用Spring Data存储库(例如for repo.save(…))时激活事务,并稍微定制事务配置查找以优先于实现类的接口,以允许存储库接口覆盖定义的事务配置SimpleJpaRepository.

  • @codemaven 如果您可以使用这个围绕 SharedEntityManagerCreator 的新解决方案更新您的答案,那就太好了。 (2认同)

cod*_*ven 13

我通过手动绑定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());
}
Run Code Online (Sandbox Code Playgroud)

必须有更好的方法.似乎很奇怪,RepositoryFactory被设计为使用EnitiyManager而不是EntityManagerFactory.我希望,它首先会查看是否EntityManger绑定到线程,然后创建一个新的绑定它,或使用现有的.

基本上,我想注入存储库代理,并希望在每次调用时它们在内部创建一个新的EntityManager,以便调用是线程安全的.