如何在同一个 Hibernate 事务中运行原生 SQL 查询?

Posted

技术标签:

【中文标题】如何在同一个 Hibernate 事务中运行原生 SQL 查询?【英文标题】:How to run native SQL queries in the same Hibernate transaction? 【发布时间】:2014-11-02 23:40:04 【问题描述】:

我们有一个服务是@Statefull。大多数数据操作都是原子的,但是在一组特定的函数中我们希望在一个事务中运行多个native queries

我们向EntityManager 注入了事务范围的持久性上下文。创建“一堆”普通实体时,使用em.persist() 一切正常。

但是当使用本机查询时(某些表没有由任何@Entity 表示)Hibernate 不会在同一个事务中运行它们,而是基本上每个查询使用一个事务。

所以,我已经尝试使用手动 START TRANSACTION;COMMIT; 条目 - 但这似乎会干扰事务,当混合原生查询和持久性调用时,hibernate 用于持久化实体。

@Statefull
class Service

   @PersistenceContext(unitName = "service")
   private EntityManager em;

   public void doSth()
      this.em.createNativeQuery("blabla").executeUpdate();
      this.em.persist(SomeEntity);
      this.em.createNativeQuery("blablubb").executeUpdate();
   

此方法中的所有内容都应在一个事务中发生。 Hibernate可以做到这一点吗? 在调试它时,可以清楚地看到每条语句都“独立”于任何事务发生。 (即,在每条语句之后立即将更改刷新到数据库。)


我已经使用最低设置测试了下面给出的示例,以消除问题的任何其他因素(字符串仅用于在每次查询后检查数据库的断点):

@Stateful
@TransactionManagement(value=TransactionManagementType.CONTAINER) 
@TransactionAttribute(value=TransactionAttributeType.REQUIRED)
public class TestService 

    @PersistenceContext(name = "test")
    private EntityManager em;

    public void transactionalCreation()
        em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate();
        String x = "test";
        em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate();
        String y = "test2";
        em.createNativeQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate();
    

Hibernate 是这样配置的:

<persistence-unit name="test">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/test</jta-data-source>

        <properties>
          <property name="hibernate.dialect" value="org.hibernate.dialect.mysql5InnoDBDialect" />

            <property name="hibernate.transaction.jta.platform"
                value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />

            <property name="hibernate.archive.autodetection" value="true" />
            <property name="hibernate.jdbc.batch_size" value="20" />
          <property name="connection.autocommit" value="false"/>
        </properties>
    </persistence-unit>

结果与自动提交模式相同:每次本机查询后,数据库(从第二个连接查看内容)立即更新。


以手动方式使用事务的想法导致相同的结果:

public void transactionalCreation()
        Session s = em.unwrap(Session.class);
        Session s2 = s.getSessionFactory().openSession();
        s2.setFlushMode(FlushMode.MANUAL);
        s2.getTransaction().begin();

        s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')").executeUpdate();
        String x = "test";
        s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','c','b')").executeUpdate();
        String y = "test2";
        s2.createSQLQuery("INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('c','b','a')").executeUpdate();

        s2.getTransaction().commit();
        s2.close();
    

【问题讨论】:

你试过this吗? 作为替代方案,您可以尝试通过调用 getSession().setFlushMode(FlushMode.MANUAL) 来阻止休眠将更改刷新到数据库,但这只是一个猜测。 感谢您的回复。 FlushMode.Manual 似乎没有任何影响。我将尝试有关直接使用会话而不是 em 的建议解决方案。 【参考方案1】:

如果你不使用container managed transactions,那么你也需要添加交易策略:

@Stateful
@TransactionManagement(value=TransactionManagementType.CONTAINER)
@TransactionAttribute(value=REQUIRED)

我只在两种情况下看到过这种现象:

DataSource 以自动提交模式运行,因此每条语句都在单独的事务中执行 EntityManager 未配置@Transactional,但随后只能运行查询,因为任何 DML 操作最终都会引发事务所需的异常。

让我们回顾一下您设置了以下 Hibernate 属性:

hibernate.current_session_context_class=JTA
transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
jta.UserTransaction=java:comp/UserTransaction

必须使用您的应用程序服务器 UserTransaction JNDI 命名键设置最终属性。

你也可以使用:

hibernate.transaction.manager_lookup_class=org.hibernate.transaction.JBossTransactionManagerLookup

或根据您当前的 Java EE 应用服务器的其他策略。

【讨论】:

是的,Hibernate 正在自动提交模式下运行。它也被配置为使用 JbossTransactionManager。我已经尝试过 - 如评论中所述 - 为某种方法将 FlushMode 设置为 Manual,但到目前为止没有任何效果。我也会尝试添加交易政策,并用结果更新我的帖子。 但是你不应该在自动提交中运行。 Hibernate 应该控制事务提交或回滚。 我测试了您提出的解决方案,但即使设置最少(请参阅更新后的帖子)每个查询都是独立运行的。 (我还在transactionalCreation() 方法上使用了各种注释,到目前为止还没有实现任何东西......你能确定,这可能使用nateiveQuery()吗? 您没有两个打开的两个会话。第一个会话已经注册了一个数据库连接,所以典型的流程是每个线程一个会话。仅使用第一个 Session,甚至使用 entityManager。它应该对在同一工作单元中执行的所有查询使用相同的连接。 感谢您到目前为止的帮助。我刚刚使用了第二个 Session 来确保上面没有任何“预配置”,这可能会导致意外结果。但结果是一样的……【参考方案2】:

您必须将&lt;hibernate.connection.release_mode key="hibernate.connection.release_mode" value="after_transaction" /&gt; 添加到您的属性中。重新启动后,事务处理是否正常工作。

【讨论】:

【参考方案3】:

在使用每个配置属性和/或注释又阅读了几个小时后,我可以为我的用例找到一个可行的解决方案。这可能不是最好的或唯一的解决方案,但由于这个问题已经收到了一些书签和赞成票,我想分享我到目前为止的内容:

起初,在托管模式下运行持久性单元时,无法使其按预期工作。 (&lt;persistence-unit name="test" transaction-type="JTA"&gt; - 如果没有给出值,JTA 是默认值。)

我决定在持久化 xml 中添加另一个持久化单元,它被配置为在非托管模式下运行:&lt;persistence-unit name="test2" transaction-type="RESOURCE_LOCAL"&gt;

(注意:关于Multiple Persistence Units的警告只是因为eclipse无法处理。它根本没有功能影响)

非托管持久性上下文需要数据库的本地配置,因为它不再是容器提供的:

<persistence-unit name="test2" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <class>test.AEntity</class>

        <properties>
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.password" value="1234"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.archive.autodetection" value="true" />
            <property name="hibernate.jdbc.batch_size" value="20" />
            <property name="hibernate.connection.autocommit" value="false" />
        </properties>
    </persistence-unit>

现在需要对项目进行更改,即在使用 @PersistenceContext 注释检索 EntityManager 的托管实例时添加 unitName

但请注意,您只能将@PersistenceContext 用于托管持久性单元。对于非托管的,您可以实现一个简单的Producer 并在需要时使用 CDI 注入 EntityManager:

@ApplicationScoped
public class Resources 

    private static EntityManagerFactory emf;

    static 
        emf = Persistence.createEntityManagerFactory("test2");
    

    @Produces
    public static EntityManager createEm()
        return emf.createEntityManager();
    

现在,在原始帖子中给出的示例中,您需要注入 EntityManager 并手动处理事务。

@Stateful
public class TestService 

    @Inject
    private EntityManager em;

    public void transactionalCreation() throws Exception 

        em.getTransaction().begin();

        try 
            em.createNativeQuery(
                    "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','a')")
                    .executeUpdate();
            em.createNativeQuery(
                    "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','b')")
                    .executeUpdate();
            em.createNativeQuery(
                    "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','c')")
                    .executeUpdate();
            em.createNativeQuery(
                    "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')")
                    .executeUpdate();

            AEntity a = new AEntity();
            a.setName("TestEntity1");
            em.persist(a);

            // force unique key violation, rollback should appear.
//          em.createNativeQuery(
//                  "INSERT INTO `ttest` (`name`,`state`,`constraintCol`)VALUES('a','b','d')")
//                  .executeUpdate();
            em.getTransaction().commit();
         catch (Exception e) 
            em.getTransaction().rollback();
        
    

到目前为止,我的测试表明,混合原生查询和持久性调用会产生预期的结果:要么提交所有内容,要么将事务作为一个整体回滚。

目前,该解决方案似乎有效。我将继续在主项目中验证它的功能并检查是否有任何其他副作用。

我需要验证的另一件事是它是否会保存到:

将两个版本的 EM 注入一个 Bean 并混合使用。 (第一次检查似乎有效,即使在同一张桌子上同时使用两个 em) EM 的两个版本都在同一个数据源上运行。 (相同的数据源很可能没有问题,我认为相同的表可能会导致意外问题。)

ps.:这是草案 1。我将继续改进答案并指出我将要发现的问题和/或缺点。

【讨论】:

以上是关于如何在同一个 Hibernate 事务中运行原生 SQL 查询?的主要内容,如果未能解决你的问题,请参考以下文章

调用 Hibernate 会话刷新

Spring 和 Hibernate 的长时间运行事务?

springboot hibernate运行原生sql语句

java - 如何在Java Spring和Hibernate的单个事务中管理2个DAO方法?

如何使用 JDBC 或 Hibernate 获取当前数据库事务 ID?

Hibernate框架学习——事务