Возможно ли иметь две единицы сохранения MSSQL в транзакции без XA?

У нас есть приложение, которое имеет ряд классов сущностей, для которых должны быть две таблицы. Таблицы идентичны, с той лишь разницей, что это имя. Обычные решения, предлагаемые здесь в SO, - это использовать наследование (сопоставленный суперкласс и стратегию таблицы за класс) или две единицы персистентности с разными сопоставлениями. Мы используем последнее решение, и приложение построено поверх этого подхода, поэтому теперь оно считается заданным.

Существуют методы EJB, которые будут выполнять обновления в обоих контекстах сохранения и должны делать это за одну транзакцию. Оба контекста сохранения имеют один и тот же источник данных, который является подключением XA к базе данных Microsoft SQL Server (версия 2012 года). Единственное различие между контекстами заключается в том, что у одного есть XML-код для изменения имен таблиц для некоторых классов сущностей и, следовательно, работает с этими таблицами.

Один из лидеров архитектуры хотел бы, чтобы транзакции XA были устранены, поскольку они вызывают существенные накладные расходы в базе данных и, по-видимому, также делают регистрацию и анализ запросов, которые выполняются более сложными, возможно, также предотвращая некоторое подготовленное кэширование инструкций. Я не знаю всех подробностей, но для многих приложений нам удалось исключить XA. Для этого, однако, мы в настоящее время не можем из-за двух контекстов персистентности.

Есть ли какой-то способ в этой ситуации, чтобы обновления обоих контекстов происходили транзакционным образом без XA? Если да, то как? Если нет, возможно ли изменение архитектуры или конфигурации для использования одного контекста персистентности без необходимости обращения к подклассам для двух таблиц?

Мне известны следующие вопросы: Можно ли использовать в транзакции несколько единиц персистентности, не имея XA? и транзакция XA для двухфазной фиксации

Перед голосованием, чтобы закрыть это как дубликат, обратите внимание, что ситуации разные. Мы не в ситуации, доступной только для чтения, как в первом вопросе, оба контекста работают в одной базе данных, мы используем только MSSQL, и мы работаем над GlassFish, а не с Weblogic.

Ответ 1

После некоторых экспериментов я обнаружил, что на самом деле возможно иметь две единицы непрерывности, которые используют ресурсы, отличные от XA, в транзакции, управляемой контейнером. Однако это может быть зависящим от реализации. TL; DR внизу.

JTA должен требовать ресурсы XA, если в транзакции участвует более одного ресурса. Он использует X/Open XA для разрешения распределенных транзакций, например, в нескольких базах данных или в базе данных и в JMS-очереди. По-видимому, существует определенная оптимизация (возможно, это зависит от GlassFish, я не уверен), что позволяет последнему участнику быть не-XA. Однако в моем случае использования обе единицы сохранения сохраняются для одной базы данных (но другой набор таблиц с некоторым возможным перекрытием), и оба они не являются XA. Это означает, что мы ожидаем, что исключение будет выбрано, если второй ресурс не поддерживает XA.

Предположим, что это наш persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    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_2_0.xsd">
    <persistence-unit name="playground" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/playground</jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="be.dkv.hibernate.SQLServer2012Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
    <persistence-unit name="playground-copy" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/playground</jta-data-source>
        <mapping-file>META-INF/orm-playground-copy.xml</mapping-file>
        <properties>
            <property name="hibernate.dialect" value="be.dkv.hibernate.SQLServer2012Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

Есть две единицы сохранения, одна с именем playground, другая с именем playground-copy. Последний имеет файл сопоставления ORM, но это немного больше, чем здесь. Важно то, что оба имеют один и тот же <jta-data-source>.

На сервере приложений (GlassFish в этом случае) у нас будет пул соединений JDBC с ресурсом JDBC с именем playground, который использует этот пул.

пул и ресурс подключения

Теперь, если в EJB вводятся два контекста персистентности и вызывается метод, который считается находящимся в транзакции, управляемой контейнером, вы ожидаете, что все будет выглядеть так.

соединения единицы продолжительности

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

Однако при попытке вышеперечисленного с источником данных указывать на пул соединений с реализацией не-XA (и выполнять некоторую фактическую работу настойчивости), не было никаких исключений, и все работало нормально! Поддержка XA на сервере MSSQL была даже отключена, и попытка использовать драйвер XA приведет к ошибке до тех пор, пока она не будет включена, поэтому мне не нравится, что я случайно использовал XA, не зная.

Переход в код с помощью отладчика показал, что оба контекста персистентности, являясь разными менеджерами сущностей (как и должны), фактически используют одно и то же соединение. Дальнейшее копание показало, что соединение не было установлено как входящее в транзакцию XA и имело тот же идентификатор транзакции на уровне JDBC. Итак, ситуация стала такой:

совместное подключение

Я могу только предположить, что поставщик JPA имеет оптимизацию для использования одного и того же соединения, если для одной транзакции создано несколько единиц. Итак, почему это будет хорошо? На уровне JDBC транзакции совершаются по соединению. Насколько мне известно, спецификация JDBC не обеспечивает метод одновременного выполнения нескольких транзакций по одному соединению. Это означает, что если работа для одного контекста персистентности совершена, фиксация также произойдет для другой.

Но это действительно , почему работает. Точка фиксации для распределенной транзакции должна действовать так, как если бы все части составляли целое (при условии, что все голосовали "да" на этапе голосования). В этом случае оба контекста персистентности работают в одном соединении, поэтому они неявно являются единицей работы. Поскольку транзакция управляется контейнером, в любом случае немедленного доступа к ней нет, то есть вы не можете перемещаться, чтобы зафиксировать один контекст, а не другой. И только с одним подключением к фактическому регистру транзакции он не должен быть XA, так как он не считается распределенным с точки зрения диспетчера транзакций.

Обратите внимание, что это не нарушает локальность контекстов персистентности. Извлечение объекта из базы данных приводит к отдельному объекту в обоих контекстах. Они могут по-прежнему работать независимо друг от друга, так же как и с отдельными соединениями. На приведенной выше диаграмме выбранные объекты того же типа с одним и тем же основным ключом представляют одну и ту же строку базы данных, но представляют собой отдельные объекты, управляемые их соответствующими менеджерами сущностей.

Чтобы убедиться, что это действительно какая-то оптимизация поставщиком JPA, я создал второй пул подключений (к той же базе данных) и отдельный ресурс JDBC, установил его для второго блока сохранения и протестировал. Это приводит к ожидаемому исключению:

Caused by: java.sql.SQLException: Error in allocating a connection. 
Cause: java.lang.IllegalStateException: Local transaction already has 1 non-XA Resource: cannot add more resources. 

Если вы создаете два ресурса JDBC, но указывайте оба на тот же пул соединений, то снова он работает отлично. Это даже работало при явном использовании класса com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource, подтверждая, что это, вероятно, оптимизация на уровне JPA, а не случайное получение того же соединения дважды для одного и того же источника данных (что приведет к поражению пула GlassFish). При использовании источника данных XA это действительно будет соединение с поддержкой XA, но поставщик JPA все равно будет использовать один и тот же для обоих контекстов персистентности. Только при использовании отдельных пулов на самом деле это будет два совершенно отдельных соединения с поддержкой XA, и вы больше не получите вышеуказанное исключение.

Итак, что за улов? Прежде всего, я не нашел ничего описания (или обязательного) такого поведения в спецификациях JPA или JTA. Это означает, что это, вероятно, оптимизация для конкретной реализации. Перейдите к другому поставщику JPA или даже к другой версии, и он больше не сможет работать.

Во-вторых, возможно получить взаимоблокировки. Если вы выберете объект в приведенном выше примере в обоих контекстах, измените его на один и выполните флеш, это нормально. Выполните его в одном контексте, вызовите метод flush, а затем попытайтесь извлечь его в другой, и у вас может быть тупик. Если вы разрешаете изолировать транзакцию без чтения, вы избегаете этого, но то, что вы увидите в одном контексте, будет зависеть от того, когда вы извлекли его в отношении флеша в другом. Таким образом, ручные запросы на флеш могут быть сложными.

Для справки использовалась версия GlassFish 3.1.2.2. Поставщик JPA был версией Hibernate 3.6.4.Final.


TL; DR

Да, вы можете использовать два контекста персистентности с одним и тем же ресурсом, отличным от XA, в транзакции, управляемой контейнером JavaEE, и сохраняются свойства ACID. Однако это связано с тем, что, вероятно, является оптимизацией Hibernate, когда несколько EntityManager создаются для одной транзакции с одним и тем же источником данных. Поскольку это не похоже на спецификации JPA или JTA, вы, вероятно, не можете полагаться на это поведение в реализациях JPA, версиях или серверах приложений. Поэтому проверьте и не ожидайте полной переносимости.