Java 如何在同一Hibernate事务中运行本机SQL查询?

Java 如何在同一Hibernate事务中运行本机SQL查询?,java,mysql,hibernate,jpa,transactions,Java,Mysql,Hibernate,Jpa,Transactions,我们提供的服务是@Statefull。大多数数据操作都是原子操作,但在一组特定的函数中,我们希望在一个事务中运行多个本机查询 我们向EntityManager注入了事务范围的持久性上下文。当创建一组普通实体时,使用em.persist()一切正常 但是当使用本机查询时(有些表不由任何@实体表示),Hibernate不会在同一事务中运行它们,但基本上每个查询使用一个事务 因此,我已经尝试使用手动启动事务和提交条目-但这似乎干扰了事务,hibernate在混合本机查询和持久性调用时使用它来持久化实体

我们提供的服务是
@Statefull
。大多数数据操作都是原子操作,但在一组特定的函数中,我们希望在一个事务中运行多个
本机查询

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

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

因此,我已经尝试使用手动
启动事务
提交条目-但这似乎干扰了事务,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中是可能的吗? 调试时,可以清楚地看到,每条语句都是“独立于”任何事务的。(即,在每条语句之后,更改都会立即刷新到数据库。)


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

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>
如果不使用,则还需要添加事务策略:

@Stateful
@TransactionManagement(value=TransactionManagementType.CONTAINER)
@TransactionAttribute(value=REQUIRED)
我只在两种情况下见过这种现象:

  • 数据源
    以自动提交模式运行,因此每个语句都在单独的事务中执行
  • 未使用
    @Transactional
    配置
    EntityManager
    ,但由于任何DML操作最终都会引发事务所需的异常,因此只能运行查询
让我们回顾一下您设置了以下Hibernate属性:

hibernate.current_session_context_class=JTA
transaction.factory_class=org.hibernate.transaction.JTATransactionFactory
jta.UserTransaction=java:comp/UserTransaction
其中,必须使用Application Server UserTransaction JNDI命名密钥设置最终属性

您还可以使用:

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

或者根据您当前的Java EE应用程序服务器采取一些其他策略。

在阅读了关于这个主题的文章之后,我又花了几个小时来研究每个配置属性和/或注释,我可以为我的用例找到一个可行的解决方案。这可能不是最好或唯一的解决方案,但由于该问题已收到一些书签和投票,我想与大家分享我迄今为止的成果:

起初,在托管模式下运行持久化单元时,无法使其按预期工作。(
-JTA是默认值,如果没有给出值。)

我决定向持久性xml添加另一个持久性单元,该持久性xml配置为在非托管模式下运行:

(注意:对多个持久性单元的担忧只是eclipse无法处理的原因。它对功能没有任何影响)

非托管Persience上下文需要对数据库进行本地配置,因为不再提供容器:

<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>
现在,在原始帖子中给出的示例中,您需要注入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并混合使用。(即使在同一张桌子上同时使用两个ems,第一次检查似乎也有效)
  • 在同一数据源上运行两个版本的EM。(相同的数据源很可能没有问题,我假设相同的表可能会导致意外问题。)

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

您必须将
添加到您的属性中。重新启动后,事务处理应该会工作。

您已经尝试过了吗?作为替代方法,您可以尝试在之前调用getSession().setFlushMode(FlushMode.MANUAL)来停止hibernate刷新对db的更改,但这只是您的响应的guess.thx<代码>刷新模式。手动
似乎没有任何影响。我将尝试建议的直接使用会话的解决方案,而不是
em
。是的,Hibernate正在自动提交模式下运行。它也被配置为使用JbossTransactionManager。我已经尝试过——正如在文章中提到的那样——为某种方法将FlushMode设置为Manual,但到目前为止没有效果。我也会尝试添加事务策略,并用结果更新我的帖子。但是你不应该在自动提交中运行。Hibernate应该控制事务提交或回滚。我测试了您提出的解决方案,但即使使用最小设置(请参阅更新的帖子),每个查询都独立运行。(我还在
transactionalCreation()
方法中使用了各种注释,但到目前为止还没有实现任何功能……您能肯定地告诉我,使用
nateiveQuery()可以做到这一点吗?)
?您没有两个打开的两个会话。第一个会话已经登记了一个数据库连接,因此典型的流程是每个线程一个会话。仅使用第一个会话,或者甚至使用entityManager。对于在同一工作单元中执行的所有查询,它应该使用相同的连接。到目前为止,谢谢您的帮助。我刚刚使用了第二个会话来确保它没有任何“预配置”,这可能会导致意外的结果。但结果是一样的。。。
<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>
@ApplicationScoped
public class Resources {

    private static EntityManagerFactory emf;

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

    @Produces
    public static EntityManager createEm(){
        return emf.createEntityManager();
    }
}
@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();
        }
    }
}