【问题标题】:How to run native SQL queries in the same Hibernate transaction?如何在同一个 Hibernate 事务中运行原生 SQL 查询?
【发布时间】: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 的建议解决方案。

标签: java mysql hibernate jpa transactions


【解决方案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。我将继续改进答案并指出我将要发现的问题和/或缺点。

    【讨论】:

      猜你喜欢
      • 2014-07-15
      • 1970-01-01
      • 2016-10-11
      • 2011-08-28
      • 1970-01-01
      • 2021-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多