如何为 EntityManagerFactory 设置 Hibernate 拦截器

How to set a Hibernate interceptor for an EntityManagerFactory

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

"Why not just use Session instead of EntityManager?"我们仍然希望减少可能的技术迁移的影响

For what do I want to use Interceptor:

我可以通过以下方式恢复问题:该项目在警报数据库上运行 运行 查询。每个地方都有一个带有警报 Table 的数据库,但客户想要一个单一的数据库,我们必须在其中创建多个 "Alarm Table",每个地方一个(例如:Table_Alarm-Place1, Table_Alarm-Place2)。这意味着我们将有多个表用于同一实体,拦截器的目标是更改休眠在最终 SQL

中生成的 Table 名称

How I pretend to use the interceptor:

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;
    }

}

为什么不简单地执行以下操作:

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在 as-needed 的基础上。

您可以在构建 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();

您似乎想要一个 multi-tenant 数据库。我之前遇到过类似的问题,并使用 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 {
}

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

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

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

覆盖 Hibernate 的 EmptyInterceptor 的一个超级简单的方法就是在属性文件中这样做

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

干杯:)