【问题标题】:Dynamically create EntityManager / Connect to custom host/DB every time in Hibernate每次在 Hibernate 中动态创建 EntityManager / Connect to custom host/DB
【发布时间】:2020-01-28 11:52:50
【问题描述】:

我有一个在 Websphere Liberty 上运行的应用程序,它应该比较来自 2 个数据库/模式的表。

用户应该能够输入连接数据,例如主机和凭据。

我正在使用 Hibernate 访问应用程序数据库。

我尝试使用多个持久性单元,一个用于应用程序数据库,一个用于所有其他数据库。

但我遇到了两个问题:

  1. 我收到“非法尝试征用多个 1PC XAResources” 有时会出错
  2. 可以查询 2 个 DB 用户提交的凭据,但我没有得到任何结果,除非我连接 到 server.xml 文件中列出的与 DataSource 相同的数据库

这是服务器上 server.xml 上的 DataSources(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>

这是 EJB 模块上的 persistence.xml

<?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 Bean 上我使用 EntityManagerFactory

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

我使用这样的自定义凭据创建实体管理器

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

如果我使用 getProperties 检查 EntityManager 属性,一切似乎都是正确的。但是只有当凭证/主机是 = 到数据源时,查询才有效。否则我没有结果(但没有错误)

可能是什么问题? 有没有办法只使用一个持久性单元,但针对不同的查询使用自定义主机/凭据?

【问题讨论】:

    标签: java hibernate entitymanager websphere-liberty


    【解决方案1】:

    我最近有同样的请求,用户可以连接到应用程序并输入他们的数据库详细信息。从此时起,每个 JPA 查询都在这个新连接上执行。

    另外一个问题是有超过 100 个碱基可供选择,而且一开始就创建所有数据源并不是一个好主意。

    因此,我们创建了一个单独的 RoutingDatasource 来管理 JDBC 连接。我们深受 Spring 的实现 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 的启发。

    主要思想是:

    • 您扩展了 AbstractDataSource 和 getConnection() 方法,该方法在每个 JPA 查询中调用。您基本上是在修补 JPA 层下的 JDBC 层。
    • 然后根据用户输入的内容创建一个新的数据源
    • 将您的路由数据源设置为实体管理器的主要数据源
    • 可选择添加一些缓存以避免每次都重新创建数据源(或实现一些池)

    这是一个演示类:

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

    【讨论】:

    • 谢谢,但是如何设置 EntityManager 数据源?请注意,我使用的是 Hibernate 4.21,而不是最新版本
    • entityManager 只使用一个数据源——RoutingDataSource,它决定在调用“getConnection”方法时连接到哪里。换句话说,EM 不知道也不关心它连接到哪里,因为作业被委托给下面的层
    【解决方案2】:

    关于第一个错误,““非法尝试登记多个 1PC XAResources”,这是因为您在同一事务中使用这两种资源。我可以在您的配置中看到 &lt;non-jta-data-source&gt;jdbc/OtherOracle&lt;/non-jta-data-source&gt;,这表明您可能打算jdbc/OtherOracle 成为非登记资源。要使其工作,数据源本身需要配置为非登记资源。您可以使用transactional="false" 属性执行此操作,如下所示:

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

    另一方面,如果您确实希望这两种资源都参与事务,那么您需要使用 XADataSource 而不是 ConnectionPoolDataSource。这是一个如何做到这一点的示例(请注意,dataSource 下的 typejdbcDriver 下的属性和类都必须为此更新:

    <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 可能会有所帮助。表注解的 JavaDoc 可以在 here 找到。

    【讨论】:

    • 谢谢,我将尝试使用 javax.sql.XADataSource。对于第二个问题,我的意思是如果在动态创建的实体管理器中我使用相同的用户密码主机等我可以连接,如果我改变它就不起作用。基本上,我在 entitymanager 配置中放置的任何内容都会被忽略并使用静态 DataSource 数据。
    • 感谢您解释第二部分。我想我明白你现在在问什么。 Hibernate 属性,例如 hibernate.connection.url、hibernate.connection.user、hibernate.connection.password、hibernate.connection.driver_class,用于独立使用(除了应用程序服务器),其中 Hibernate 必须自己建立连接。当提供 JNDI 名称时,这些将被忽略,这表明改为使用应用程序服务器提供的数据源。
    猜你喜欢
    • 2013-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-16
    • 2021-01-26
    • 2017-12-05
    • 2020-11-17
    • 2015-01-01
    相关资源
    最近更新 更多