如何为 EntityManagerFactory 设置 Hibernate 拦截器

Posted

技术标签:

【中文标题】如何为 EntityManagerFactory 设置 Hibernate 拦截器【英文标题】:How to set a Hibernate interceptor for an EntityManagerFactory 【发布时间】:2016-01-07 12:44:04 【问题描述】:

我正在使用 JPA,但我需要解开我的 EntityManagerFactory,所以我可以向 Session 添加一个拦截器。之后我想将 Session 包装回 EntityManager。

“为什么不直接使用 Session 而不是 EntityManager?”我们仍然希望减少可能的技术迁移的影响

我想用Interceptor做什么:

我可以通过以下方式恢复问题:该项目在警报数据库上运行查询。每个地方都有一个带有警报表的数据库,但客户希望拥有一个数据库,我们必须在其中创建多个“警报表”,每个地方一个(例如:Table_Alarm-Place1,Table_Alarm-Place2)。这意味着我们将为同一个实体有多个表,拦截器的目标是更改最终 SQL 中由 hibernate 生成的表名称

我如何假装使用拦截器:

public class SqlInterceptor extends EmptyInterceptor 

    private String tableSufix;

    private static final Logger LOGGER = LoggerFactory.getLogger(SqlInterceptor.class);

    public SqlInterceptor(String tableSufix) ...

    @Override
    public String onPrepareStatement(String sql) 
        String finalSql;

        //Manipulated SQL (parsed by Hibernate)

        return finalSql;
    


项目使用 JPA 2.1 和 Hibernate 4.3.11.Final

【问题讨论】:

回绕是什么意思? 我可能不够清楚,我正在做“entityManagerFactory.unwrap(SessionFactory.class)”并用它打开一个会话。现在我需要将会话“转换”回 EntityManager 我使用术语“包装”因为 EntityManager 的“unwrap()”功能 @RafaelTeles 你能否指定再次包装它的原因以便更清楚。 我正在使用的项目使用 JPA,因此所有代码都使用了 EntityManager。我有一个创建我使用的 EntityManager 实例的工厂,如果我将其更改为 Session 我需要将项目的其余部分更改为 session 它真的必须是一个会话范围的拦截器吗?您可以改用 SessionFactory 范围的吗?第三,那个拦截器要做什么,也许还有其他更优雅的方法来实现这一点? 【参考方案1】:

您可以在构建EntityManagerFactory 时提供Interceptor

String persistenceUnitName = ...;
PersistenceUnitInfo persistenceUnitInfo = persistenceUnitInfo(persistenceUnitName);
Map<String, Object> configuration = new HashMap<>();
configuration.put(AvailableSettings.INTERCEPTOR, new SqlInterceptor());

EntityManagerFactoryBuilderImpl entityManagerFactoryBuilder = new EntityManagerFactoryBuilderImpl(
    new PersistenceUnitInfoDescriptor(persistenceUnitInfo), configuration
);
EntityManagerFactory emf = entityManagerFactoryBuilder.build();

【讨论】:

【参考方案2】:

您似乎想要拥有一个多租户数据库。我之前遇到过类似的问题,并使用 aspectj 实现了一个拦截器来正确设置过滤器。即使您不使用过滤器选项,您也可以在每次使用 aspectj 创建会话时获取会话,如下所示。

public privileged aspect MultitenantAspect 

    after() returning (javax.persistence.EntityManager em): execution (javax.persistence.EntityManager javax.persistence.EntityManagerFactory.createEntityManager(..)) 
        Session session = (Session) em.getDelegate();
        Filter filter = session.enableFilter("tenantFilter");
        filter.setParameter("ownerId", ownerId);
    


在下面的示例中,我只是在需要过滤的实体上设置了需要配置的过滤器:

@Entity
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "ownerId", type = "long"))
@Filters(
    @Filter(name = "tenantFilter", condition = "(owner=:ownerId or owner is null)")
)
public class Party 

当然,要使用过滤器而不是表名,您必须添加一列来区分表 - 我认为这比使用多个表名更好。

【讨论】:

"公共特权方面 MultitenantAspect ",这是 Java 吗?【参考方案3】:

覆盖 Hibernate 的 EmptyInterceptor 的一个超级简单的方法就是在属性文件中使用它

spring.jpa.properties.hibernate.session_factory.interceptor=<fully-qualified-interceptor-class-name>

干杯:)

【讨论】:

【参考方案4】:

您可以将所有必要的信息存储在静态ThreadLocal 实例中,然后再读取。

这样可以避免会话范围拦截器的复杂性,并且可以使用其他机制来实现相同的目标(例如使用更容易配置的会话工厂范围拦截器)。

【讨论】:

【参考方案5】:

为什么不简单地做以下事情:

EntityManagerFactory entityManagerFactory = // created from somewhere.
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
// do whatever you need with the session factory here.

// Later in your code, initially use EntityManager and unwrap to Session.
EntityManager entityManager = entityManagerFactory.createEntityManager();
Session session = entityManager.unwrap(Session.class);

基本上,与其尝试获取Session,然后将其包装回EntityManager,不如简单地传递一个JPA EntityManager,然后根据需要将其解包到Session

【讨论】:

我需要在 Session 中添加一个休眠拦截器。会话打开后,我没有找到这样做的方法。

以上是关于如何为 EntityManagerFactory 设置 Hibernate 拦截器的主要内容,如果未能解决你的问题,请参考以下文章

Spring + EntityManagerFactory +Hibernate 监听器 + 注入

创建 EntityManagerFactory 时出现 ArrayIndexOutOfBoundsException

从休眠配置创建 EntityManagerFactory

EntityManagerFactory 和 SessionFactory 的区别? [复制]

Spring Boot - “创建名为 'entityManagerFactory' 的 bean 时出错” - 开始

Spring Boot - “创建名为 'entityManagerFactory' 的 bean 时出错” - 开始