Динамически создавать EntityManager / Connect на пользовательский хост / БД каждый раз в Hibernate - PullRequest
1 голос
/ 28 января 2020

У меня есть приложение, работающее на Websphere Liberty, которое должно сравнивать таблицы из 2 баз данных / схем.

Пользователь должен иметь возможность вводить данные соединения, такие как хост и учетные данные.

Я использую Hibernate для доступа к базе данных приложения.

Я пытался использовать несколько единиц персистентности, одну для базы данных приложения и одну для всех других баз данных.

Но я получил 2 проблемы:

  1. я получаю ошибку "Недопустимая попытка зачисления нескольких 1P C XAResources" иногда
  2. может запросить 2 БД с предоставленными пользователем учетными данными, но я не получаю результатов за исключением случаев, когда я подключаюсь к той же базе данных, указанной на сервере. xml файл, что и DataSource

Это источники данных на сервере. xml на сервере (dbs oracle dbs)

<dataSource id="MyAppDS" jndiName="jdbc/MyDS" type="javax.sql.ConnectionPoolDataSource">
    <jdbcDriver javax.sql.ConnectionPoolDataSource="oracle.jdbc.pool.OracleConnectionPoolDataSource" libraryRef="OracleSQLLib"/>
    <connectionManager agedTimeout="30m" connectionTimeout="10s" maxPoolSize="20" minPoolSize="5"/>
    <properties password="..." url="jdbc:oracle:thin:@...:1521:..." user="..."/>
</dataSource>
<dataSource id="OtherOracle" jndiName="jdbc/OtherOracle" type="javax.sql.ConnectionPoolDataSource">
    <jdbcDriver javax.sql.ConnectionPoolDataSource="oracle.jdbc.pool.OracleConnectionPoolDataSource" libraryRef="OracleSQLLib"/>
    <connectionManager agedTimeout="30m" connectionTimeout="10s" maxPoolSize="20" minPoolSize="5"/>
    <properties password="..." url="jdbc:oracle:thin:@127.0.0.1:1521:XE" user="..."/>
</dataSource>

Это постоянство. xml в модуле EJB

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="main-persistence">
    <jta-data-source>jdbc/MyDS</jta-data-source>

    <class>classes...</class>

    <properties>
        <property name="hibernate.transaction.jta.platform"
            value="org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform" />
        <property name="hibernate.dialect"
            value="org.hibernate.dialect.Oracle9iDialect" />
        <property name="hibernate.temp.use_jdbc_metadata_defaults"
            value="false" />
    </properties>
</persistence-unit>
<persistence-unit name="other-persistence" transaction-type="RESOURCE_LOCAL">
    <non-jta-data-source>jdbc/OtherOracle</non-jta-data-source>
    <class>classes...</class>

    <properties>
        <property name="hibernate.transaction.jta.platform"
            value="org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform" />
        <property name="hibernate.dialect"
            value="org.hibernate.dialect.Oracle9iDialect" />
        <property name="hibernate.temp.use_jdbc_metadata_defaults"
            value="false" />
    </properties>
</persistence-unit>

В компоненте Java я использую EntityManagerFactory

    @PersistenceUnit(unitName = "other-persistence")
    private EntityManagerFactory emf;

И я создаю менеджер сущностей с пользовательским кредитом. enals как это

    Map<String, String> properties = new HashMap<String, String>();
    properties.put("hibernate.connection.driver_class", "oracle.jdbc.OracleDriver");
    properties.put("hibernate.connection.url", myCustomCreatedConnectionUrl);
    properties.put("hibernate.connection.username", customUser);
    properties.put("hibernate.connection.password", customPassword);
    properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle9iDialect");
    properties.put("hibernate.show-sql", "true");
    EntityManager entityManager = emf.createEntityManager(properties);

Если я проверяю свойства EntityManager с getProperties, все кажется правильным. Но запросы работают только в том случае, если учетные данные / хост = для источника данных. В противном случае я не получаю результатов (но без ошибок)

В чем может быть проблема? Есть ли способ использовать только один модуль сохраняемости, но с настраиваемым хостом / учетными данными для разных запросов?

Ответы [ 2 ]

1 голос
/ 28 января 2020

У меня недавно был такой же запрос, когда пользователь может подключиться к приложению и ввести данные своей базы данных. С этого момента каждый запрос JPA выполняется на этом новом соединении.

Дополнительная проблема заключалась в том, что было доступно более 100 баз, и создание всех источников данных при запуске не было хорошей идеей.

Таким образом, мы создали один RoutingDatasource, который управляет соединениями JDB C. Мы были глубоко вдохновлены реализацией Spring org.springframework.jdb c .datasource.lookup.AbstractRoutingDataSource .

Основная идея:

  • Вы расширяете AbstractDataSource и getConnection () метод, который вызывается при каждом запросе JPA. Вы в основном работаете со слоем JDB C под слоем JPA.
  • Затем вы создаете новый источник данных в соответствии с тем, что ввел ваш пользователь
  • Установите свой источник данных маршрутизации в качестве основного источника данных для менеджера сущностей
  • При желании добавьте некоторое кэширование, чтобы избежать повторного создания источников данных каждый раз (или реализуйте некоторый пул)

Вот демонстрационный класс:

public class RoutingDataSource extends AbstractDataSource {

  ...
  ...

  @Override
  public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
  }

  DataSource determineTargetDataSource() {
    EmployeeDatabase lookupKey = determineCurrentLookupKey();

    DataSource dataSource = this.datasources.get(lookupKey);
    if (dataSource == null) {
      logger.debug("Datasource not found. Creating new one");

      SQLServerDataSource newDatasource = new SQLServerDataSource();
      newDatasource.setURL("jdbc:sqlserver://" + lookupKey.getDatabaseHost());
      newDatasource.setPassword(dbPass);
      datasources.put(lookupKey, newDatasource);

      dataSource = newDatasource;
    } else {
      logger.debug("Found existing database for key " + lookupKey);
    }

    logger.debug("Connecting to " + dataSource);
    return dataSource;
  }
}
1 голос
/ 28 января 2020

Относительно первой ошибки, "" Недопустимая попытка зачисления нескольких 1P C XAResources ", это происходит потому, что вы используете оба ресурса в одной транзакции. Я вижу <non-jta-data-source>jdbc/OtherOracle</non-jta-data-source> в вашей конфигурации, что указывает на то, что вы можете намеревались сделать jdbc / Other Oracle не подлежащим привлечению ресурсов. Чтобы это работало, сам источник данных должен быть настроен как не подлежащий привлечению. Это можно сделать с помощью атрибута transactional="false" следующим образом:

<dataSource id="OtherOracle" jndiName="jdbc/OtherOracle" type="javax.sql.ConnectionPoolDataSource" transactional="false">
...

С другой стороны, если вы действительно хотите, чтобы оба ресурса были зачислены в транзакцию, вам нужно использовать XADataSource, а не ConnectionPoolDataSource. Вот пример того, как это сделать (обратите внимание, что оба типа type в dataSource и атрибут и класс в jdbcDriver должны быть обновлены для этого:

<dataSource id="MyAppDS" jndiName="jdbc/MyDS" type="javax.sql.XADataSource">
    <jdbcDriver javax.sql.XADataSource="oracle.jdbc.xa.client.OracleXADataSource" libraryRef="OracleSQLLib"/>
    <connectionManager agedTimeout="30m" connectionTimeout="10s" maxPoolSize="20" minPoolSize="5"/>
    <properties password="..." url="jdbc:oracle:thin:@...:1521:..." user="..."/>
</dataSource>
<dataSource id="OtherOracle" jndiName="jdbc/OtherOracle" type="javax.sql.XADataSource">
    <jdbcDriver javax.sql.XADataSource="oracle.jdbc.xa.client.OracleXADataSource" libraryRef="OracleSQLLib"/>
    <connectionManager agedTimeout="30m" connectionTimeout="10s" maxPoolSize="20" minPoolSize="5"/>
    <properties password="..." url="jdbc:oracle:thin:@127.0.0.1:1521:XE" user="..."/>
</dataSource>

Во втором вопросе, я думаю, вы говорите, что разные пользователи не могут видеть данные. Может ли это быть потому, что разные пользователи базы данных используют разные схемы и не имеют доступа друг к другу? данные? Если вы можете получить всех пользователей, используя общую схему, то добавление @Table(schema="YOUR_SCHEMA_NAME") в JPA @Entity может помочь. JavaDo c для аннотации таблицы можно найти здесь .

...