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

时间:2014-09-09 13:55:48

标签: java mysql hibernate jpa transactions

我们的服务是@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();
    }

3 个答案:

答案 0 :(得分:4)

如果您不使用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

必须使用Application Server UserTransaction JNDI命名键设置final属性。

你也可以使用:

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

或根据您当前的Java EE Application Server的其他策略。

答案 1 :(得分:1)

在阅读了关于每个配置属性和/或注释的另外一小时的主题后,我可以找到一个适用于我的用例的工作解决方案。它可能不是最好或唯一的解决方案,但由于问题已收到一些书签和赞成,我想分享我到目前为止所拥有的:

首先,在托管模式下运行持久性单元时,无法按预期工作。 (<persistence-unit name="test" transaction-type="JTA"> - 如果没有给出值,则JTA是默认值。)

我决定在持久性xml中添加另一个持久性单元,它被配置为以非托管模式运行:<persistence-unit name="test2" transaction-type="RESOURCE_LOCAL">

(注意:关于多个持久性单元的警告只是因为eclipse无法处理。它根本没有功能影响)

非托管persitence-context需要数据库的本地配置,因为它不再是容器提供的:

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

现在,只要您使用unitName注释检索EntityManager的托管实例,就可以添加@PersistenceContext项目所需的更改。

但请注意,您只能将@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并混合使用。 (即使在同一张桌子上同时使用两个ems,第一次检查似乎也有效)
  • 让两个版本的EM在同一数据源上运行。 (相同的数据源很可能没有问题,我认为相同的表可能会导致意外问题。)

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

答案 2 :(得分:0)

您必须将<hibernate.connection.release_mode key="hibernate.connection.release_mode" value="after_transaction" />添加到您的媒体资源中。重启后应该进行事务处理工作。