Собственный запрос executeUpdate фиксирует данные в транзакции Spring

Мой пример использования следующий: при создании пользователя приложения (EntityManager.persist) я также должен создать пользователя БД и предоставить ему привилегии (поэтому мне нужен hibernate nativeQuery).

У меня есть метод Spring @Transactional, который вызывает мой DAO с обоими вызовами:

@Transactional
public Integer createCompany(Company company) throws Exception {
    companyDao.createReportUser(ReportUser user);
    ...
}

Мой метод DAO выглядит так:

getEm().persist(companyReportsUser);
getEm().createNativeQuery("CREATE USER user1@localhost IDENTIFIED BY :password").setParameter("password", password).executeUpdate();
getEm().createNativeQuery("GRANT SELECT ON appdb.v_company TO user1@localhost").executeUpdate();
//several grants

Теперь, как только будет выполнена первая строка с executeUpdate(), я смогу увидеть постоянную компанию companyReportsUser в базе данных вместе с пользователем БД (user1@localhost).

Все nativeQueries выполняются и немедленно передаются по одному. Поскольку они зафиксированы, их нельзя откатить. В моей конфигурации не задано ни одного параметра автоматической фиксации, поэтому я предполагаю, что это "ложь", как указано в документах Hibernate.

  1. Я протестировал поведение @Transactional без собственных запросов, и он работает как положено (транзакция откатывается, когда я генерирую исключение RuntimeException, и данные не добавляются в базу данных)

  2. При отладке я видел, что операция persist задерживает выполнение, когда она вызывается в запущенной транзакции.

  3. Собственный запрос, кажется, немедленно создает и выполняет PreparedStatement (по крайней мере, я не нашел никакой очереди).

  4. Я полагаю, что я не могу получить взаимодействие между собственным запросом hibernate и транзакцией Spring, но я потратил время на чтение документации Spring и Hibernate относительно транзакций и собственных запросов и не нашел ничего, что могло бы мне помочь.

  5. Может быть, есть лучший способ создать пользователя базы данных и предоставить привилегии, чем собственные запросы (хотя я не нашел ни одного)

Ниже мой конфиг приложения:

applicationContext.xml

<tx:annotation-driven transaction-manager="txManager" />

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory">
        <ref local="entityManagerFactory" />
    </property>
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceUnitName" value="domainPU" />
    <property name="loadTimeWeaver">
        <bean
            class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="${db.url}" />
    <property name="username" value="${db.user.name}" />
    <property name="password" value="${db.user.password}" />
    <property name="validationQuery" value="select 1 as dbcp_connection_test" />
    <property name="testOnBorrow" value="true" />
</bean>

persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="domainPU" transaction-type="RESOURCE_LOCAL">

    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <class>com.domain.Entity1</class>
    ....

    <exclude-unlisted-classes>true</exclude-unlisted-classes>

    <properties>
        <property name="hibernate.show_sql" value="true" />
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
        <property name="hibernate.jdbc.batch_size" value="100"></property>
        <property name="hibernate.order_inserts" value="true"></property>
        <property name="hibernate.order_updates" value="true"></property>

        <property name="hibernate.c3p0.min_size" value="5"></property>
        <property name="hibernate.c3p0.max_size" value="30"></property>
        <property name="hibernate.c3p0.timeout" value="300"></property>
        <property name="hibernate.c3p0.max_statements" value="100"></property>
        <property name="hibernate.c3p0.idle_test_period" value="${hibernate.c3p0.idle_test_period}"></property>
    </properties>
</persistence-unit>

Использованные библиотеки:

  • Hibernate 4.1.6.FINAL
  • Весна 3.2.2. ВЫПУСК
  • MySQL-разъем-Java-5.1.21
  • MySQL 5.5

1 ответ

Решение

После более глубокого изучения того, как транзакции работают в MySQL, я нашел ответ:

Проблема заключалась в конкретных утверждениях внутри собственного SQL-запроса.

Из документации MySQL:

13.3.3 Утверждения, которые вызывают неявную фиксацию

  • Операторы языка определения данных (DDL), которые определяют или изменяют объекты базы данных (...CREATE TABLE, DROP DATABASE...)

  • Заявления, которые неявно используют или изменяют таблицы в базе данных mysql (CREATE USER, DROP USER и RENAME USER..., GRANT, REVOKE,...)

  • ...

Подробнее здесь:

http://dev.mysql.com/doc/refman/5.1/en/implicit-commit.html

Я решил разделить это действие на две части:

  • обычные операторы гибернации (внутри транзакции),
  • нативные операторы запроса

и предоставить пользователю инструмент / действие для повторного вызова второй части в случае, если что-то пойдет не так.

Другим решением будет переход на другую СУБД, которая поддерживает транзакции вокруг операций DDL.

Другие вопросы по тегам