当前位置: 首页 > 知识库问答 >
问题:

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

吴丁雷
2023-03-14

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

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

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

因此,我已经尝试使用手动启动事务 和提交 条目-但这似乎会干扰事务,hibernate在混合本机查询和持久性调用时用于持久化实体。

@Stateful
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();
    }

共有3个答案

别兴国
2023-03-14

您必须添加<代码>

邹弘
2023-03-14

在阅读了关于这个主题的另一段时间后,我在玩弄每一个配置属性和/或注释时,都能找到一个适用于我的用例的有效解决方案。这可能不是最好的或唯一的解决方案,但由于这个问题已经收到了一些书签和投票,我想与大家分享我到目前为止的成果:

起初,在托管模式下运行持久性单元时,无法使其按预期工作。(<代码>

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

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

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

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

项目现在需要的更改是,每当您使用@PeristenceContext注释来检索EntityManager的托管实例时,您都要添加unitName

但请注意,您只能将@PeristenceContext用于托管的持久性单元。对于非托管的,您可以实现一个简单的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并混合使用。(即使在同一张桌子上同时使用两个ems,第一次检查似乎也有效)

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

何楷
2023-03-14

如果不使用容器管理的事务,则还需要添加事务策略:

@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应用服务器使用其他策略。

 类似资料:
  • 问题内容: 我有一个带有数据库转储的SQL脚本。如何使用Hibernate执行它? 我这样尝试过: 但是它仅在包含单个SQL查询且我需要运行多个插入和其他复杂内容时才有效。 RDBMS:Oracle数据库11g快捷版11.2.0.2.0版-64位生产 问题答案: 用开始结束块包装查询。喜欢

  • 问题内容: 我有一个本机SQL查询,如下所示: 问题是,Hibernate的解释,,作为参数,并希望我们给值,, 但是我不想让Hibernate这样解释,所以我想逃避冒号。 怎么做 ? 问题答案: 在Hibernate中,转义以开头。但是在Java中,您还必须逃脱另一个。因此,所有内容都需要替换为。最后,您得到:

  • 问题内容: 我是JDBC的新手,我正在尝试更新数据库中的2个表,因此我想在1个事务中进行操作,因此,如果一个查询失败,另一个查询也会失败。我想提供这样的行为,或者如果其中一个失败,则有机会进行回滚。 这是我的两个查询: 问题答案: 如果要自动执行多个语句,则需要使用一个事务。JDBC连接默认为“自动提交”模式,这意味着每个语句都在其自己的事务中执行。因此,您首先需要使用禁用自动提交模式。 在禁用自

  • 本文向大家介绍Spring如何在一个事务中开启另一个事务,包括了Spring如何在一个事务中开启另一个事务的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了Spring如何在一个事务中开启另一个事务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 spring使用@Transactional开启事务,而且该注解使用propagation属

  • 问题内容: 当photoId直接在语句上而不是变量时,以下代码完全没有问题。 但是以下内容将无法正常运行,这可能是导致未选择该内容的原因。 这是在while循环内。 谢谢。 问题答案: 在php a和a中有很大的不同,查询语法在查询周围是双引号,在变量周围是单引号。。尽管我建议您在查询中使用参数,而不是直接将变量直接放入查询中 根据我的建议,您应该将查询更改为此: 这只是它的基本要点…如果您想了解

  • 问题内容: 我想使用Hibernate从文件运行本机SQL。SQL可以包含创建数据库结构的多个语句(即表,约束,但没有insert / update / delete语句)。 示例,下面是一个非常简单的查询(包含以下两个SQL语句) 我正在使用MySQL数据库,当我运行上述查询时,我返回了gettng语法错误。当我一一运行它们时,就可以了。 下面是运行查询的代码(上面的语句已分配给“ sql”变量