【问题标题】:@PersistenceContext is not applicable to parameters. How to inject EntityManager through a constructor?@PersistenceContext 不适用于参数。如何通过构造函数注入EntityManager?
【发布时间】:2019-02-01 11:51:26
【问题描述】:

使用构造函数注入是最佳实践。但是我无法通过@PersistenceContext 实现这一点。

我想要以下构造函数:

private final EntityManager entityManager;

@Autowired
public MyService(@PersistenceContext EntityManager entityManager) {
    this.entityManager = entityManager;
}

但我不能,因为@PersistenceContext 仅适用于TYPEMETHODFIELD

问:如何通过构造函数注入容器管理EntityManager

【问题讨论】:

  • 我不太清楚你的意思,不删除@PersistenceContext 对你有用吗?通过@Autowired 注入EntityManager 应该也能正常工作
  • @crizzis 是否会注入与@PersistenceContext 相同的实体管理器?我找不到任何文件证明这一点。
  • 如果您使用的是 spring boot:可能是的,因为 spring 是您的容器。如果您在某个应用程序服务器中使用 Spring MVC:可能不是因为 @Autowired 搜索 any matching bean,这意味着您 可能非常好抓住容器管理的EntityManager。如果您指定自定义EntityManagerFactory,则可以确保获得自己的

标签: java spring hibernate spring-boot jpa


【解决方案1】:

您似乎正在使用 spring,因此您的解决方案将相当简单:

@Component
@Scope("prototype")
public class MyPersistenceContainer
{
@PersistenceContext
private EntityManager em;

public EntityManager getEntityManager()
{
return em;
}
}

现在你可以简单地在你的构造函数中注入这个类的一个实例,它将总是持有一个有效的 EntityManager(因为 bean 范围)。请注意:在 Web 环境中,您可能应该使用 @SessionScope 甚至 @RequestScope 而不是原型,这将节省资源


但有一点需要考虑:

当使用依赖于 bean 的单例范围的 bean 时 范围为原型,请注意依赖项是 在实例化时解决。这意味着如果你依赖 将原型范围的 bean 注入到单例范围的 bean 中,一个品牌 将实例化新的原型 bean,然后注入依赖项 进入单例豆......但仅此而已。完全相同的原型 instance 将是唯一提供给 单例范围的 bean,如果这是你想要的,那很好。

但是,有时您真正想要的是单例范围 bean 能够获得一个全新的实例 在运行时一次又一次地原型范围的 bean。在那里面 如果仅依赖注入原型范围的bean是没有用的 进入你的单例bean,因为如上所述,只有 当 Spring 容器实例化单例时发生一次 bean 并解析和注入其依赖项。如果你在 您需要获取(原型)的全新实例的场景 bean在运行时一次又一次,你被称为 标题为第 4.3.7 节“方法注入”的部分

因此,如果您想将“实体管理器容器 bean”注入单例 bean(这是默认范围),请查看 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-method-injection


正确设置作用域非常重要,否则您可能会(并且将会遇到)数据库不一致、死锁或更糟糕的情况

【讨论】:

  • 请注意,问题是“如何通过构造函数注入来注入容器管理的EntityManager?”,而不是如何创建请求范围组件。 , here are some advantages 使用构造函数代替 field dep 注入
  • 对不起,我现在明白了。您谈到了会话范围,但默认情况下并非所有 Spring 组件都是单例的?我使用 Web 应用程序并且对此没有任何问题,因为这些组件是无状态的,EntityManager 和 JdbTemplate 有一些状态,但它们为此使用 ThreadLocal
  • 这正是问题所在 - 单例 bean 可以保持一个状态 并且 webapps 应该是状态less - 这将导致可能重复使用如果您没有正确选择范围,则缓存 EntityManager。重新使用实体管理器通常是一个坏主意,它会导致丢失更新和并发问题。另外:大多数容器会在数据库会话结束后关闭实体管理器,在这种情况下,它甚至无法再使用它
  • 感谢您的回答,但在您的解决方案中您仍然通过字段注入注入实体管理器,这正是我想在我的代码中摆脱的。
  • 您可以将字段注入替换为方法注入,甚至可以进行程序化容器查找,但我不明白这一点 - CDI 现在非常稳定、经过验证且高效,它将是真的 很难打败它;甚至 Spring CDI 也一样好
【解决方案2】:

使用 Spring Data 应该是可能的。但是,如果您出于某种原因不想在项目中使用 Spring Data(例如,您只是让遗留项目变得更好一点),您可以创建以下 FactoryBean 以通过构造函数注入使 EntityManager 可注入:

/**
 * Makes the {@link EntityManager} injectable via <i>@Autowired</i>,
 * so it can be injected with constructor injection too.
 * (<i>@PersistenceContext</i> cannot be used for constructor injection.)
 */
public static class EntityManagerInjectionFactory extends AbstractFactoryBean<EntityManager> {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Class<?> getObjectType() {
        return EntityManager.class;
    }

    @Override
    protected EntityManager createInstance() {
        return entityManager;
    }

}

请注意,因为我们在内部使用了@PersistenceContext 注解,所以返回的EntityManager 将是一个适当的线程安全代理,因为它会通过字段注入直接在使用位置注入。

【讨论】:

    猜你喜欢
    • 2017-05-19
    • 2011-06-10
    • 2016-01-30
    • 2018-01-17
    • 2012-05-08
    • 1970-01-01
    • 2010-11-28
    • 2018-12-12
    • 2018-03-27
    相关资源
    最近更新 更多