在纯 JPA 设置中获取数据库连接

Posted

技术标签:

【中文标题】在纯 JPA 设置中获取数据库连接【英文标题】:Getting Database connection in pure JPA setup 【发布时间】:2011-03-30 10:33:52 【问题描述】:

我们有一个 JPA 应用程序(使用休眠),我们需要将调用传递给需要 JDBC 数据库连接作为参数的旧报告工具。有没有一种简单的方法可以访问休眠已设置的 JDBC 连接?

【问题讨论】:

【参考方案1】:

根据hibernate 文档here,

连接连接()

已弃用。 (计划在 4.x 中删除)。更换视需要而定;用于直接使用 JDBC 的东西 doWork(org.hibernate.jdbc.Work) ...

改用 Hibernate Work API:

Session session = entityManager.unwrap(Session.class);
session.doWork(new Work() 

    @Override
    public void execute(Connection connection) throws SQLException 
        // do whatever you need to do with the connection
    
);

【讨论】:

以这种方式获得对连接的访问​​对我有用...但我在使用conn.rollback(savepoint) 时遇到问题,如***.com/q/45672431/173689 中所述【参考方案2】:

您想从哪里获得这种联系尚不清楚。一种可能性是从 EntityManager 使用的底层 Hibernate Session 获取它。使用 JPA 1.0,您必须执行以下操作:

Session session = (Session)em.getDelegate();
Connection conn = session.connection();

请注意,getDelegate() 不可移植,此方法的结果是特定于实现的:上面的代码在 JBoss 中工作,对于 GlassFish,您必须对其进行调整 - 看看 Be careful while using EntityManager.getDelegate()。

在 JPA 2.0 中,情况要好一些,您可以执行以下操作:

Connection conn = em.unwrap(Session.class).connection();

如果您在容器内运行,您还可以对配置的DataSource 执行查找。

【讨论】:

当我执行其中任何一个操作时,都会收到弃用警告。如果有必要,我可以忍受它,但它有点烦人(:我不想这样做,它只是务实的最简单的方法来让事情顺利进行。 是的,connection() 在 Hibernate 3.x 中已被弃用,他们正在更改 API(我认为您不会喜欢新的 API 用于您的用例)。但是计划在 Hibernate 4.x 中进行更改,这会给您一些时间。 使用 EclipseLink 似乎不可行。您无法从会话中获得连接。不过,您似乎可以解开连接 - Connection conn = em.unwrap(Connection.class)。怀疑它是否便携... 方法调用结束后需要关闭这个连接吗?还是容器知道该怎么做? 博客的链接现已失效。它曾经有什么?【参考方案3】:

如果您使用的是 JAVA EE 5.0,最好的方法是使用 @Resource 注解将数据源注入到类(例如 EJB)的属性中以保存数据源资源(例如 Oracle datasource) 用于旧版报告工具,这样:

@Resource(mappedName="jdbc:/OracleDefaultDS") DataSource datasource;

稍后您可以获取连接,并以这种方式将其传递给旧报告工具:

Connection conn = dataSource.getConnection();

【讨论】:

+1 我认为这是唯一正确的解决方案。其他依赖于不可移植的、特定于产品的(Hibernate、EclipseLink)代码。 很好的解决方案,以及在(问题的)框外思考的荣誉。顺便说一句,如果您使用 Spring 而不是 Java EE 别名 Jakarta EE,则类似的解决方案有效:将您的数据源配置为 Spring bean(您可能应该这样做),然后注入它。【参考方案4】:

如果您使用 EclipseLink: 您应该在 JPA 事务中访问连接

entityManager.getTransaction().begin();
java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class);
...
entityManager.getTransaction().commit();

【讨论】:

Hibernate 并非如此,仅适用于 EclipseLink。 不确定这是否相关,但截至 2019 年 7 月,使用 eclipselink 2.5.0 和 querydsl 4.2.1,建议的 unwrap(Connection.class) 方法在我自己的项目中返回 null。可能的解决方法:创建所需的 JPASQLQuery 并将现有 entityManager 作为参数传递(请参阅 querydsl.com/static/querydsl/4.2.1/apidocs/com/querydsl/jpa/‌​sql/...) 这对我有用。我正在将我的应用程序部署到 Java EE 应用程序服务器,但我想在 JUnit 中使用独立的 JPA/EclipseLink。 unwrap(Connection.class) 在 JUnit 中返回 null,但如果我在 @BeforeEach 方法中启动事务,它就可以工作。【参考方案5】:

休眠 4 / 5

Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> doSomeStuffWith(connection));

【讨论】:

【参考方案6】:

由于@Pascal 建议的代码如@Jacob 所述已被弃用,我发现this another way 对我有用。

import org.hibernate.classic.Session;
import org.hibernate.connection.ConnectionProvider;
import org.hibernate.engine.SessionFactoryImplementor;

Session session = (Session) em.getDelegate();
SessionFactoryImplementor sfi = (SessionFactoryImplementor) session.getSessionFactory();
ConnectionProvider cp = sfi.getConnectionProvider();
Connection connection = cp.getConnection();

【讨论】:

getConnectionProvider()org.hibernate.engine.spi.SessionFactoryImplementor 中已弃用 getConnectionProvider() 在 Hibernate 5 中不再可用。【参考方案7】:

pure这个词与hibernate这个词不匹配。

EclipseLink

Getting a JDBC Connection from an EntityManager

上面链接中描述的有点简单。

注意EntityManager 必须与Transaction 连接,否则unwrap 方法将返回null。 (根本不是一个好的举动。) 我不确定关闭连接的责任。
// --------------------------------------------------------- EclipseLink
try 
    final Connection connection = manager.unwrap(Connection.class);
    if (connection != null)  // manage is not in any transaction
        return function.apply(connection);
    
 catch (final PersistenceException pe) 
    logger.log(FINE, pe, () -> "failed to unwrap as a connection");

休眠

基本上应该通过以下代码完成。

// using vendor specific APIs
final Session session = (Session) manager.unwrap(Session.class);
//return session.doReturningWork<R>(function::apply);
return session.doReturningWork(new ReturningWork<R>() 
    @Override public R execute(final Connection connection) 
        return function.apply(connection);
    
);

好吧,我们(至少我)可能不想要任何特定于供应商的依赖项。 Proxy 来救援。

try 
    // See? You shouldn't fire me, ass hole!!!
    final Class<?> sessionClass
            = Class.forName("org.hibernate.Session");
    final Object session = manager.unwrap(sessionClass);
    final Class<?> returningWorkClass
            = Class.forName("org.hibernate.jdbc.ReturningWork");
    final Method executeMethod
            = returningWorkClass.getMethod("execute", Connection.class);
    final Object workProxy = Proxy.newProxyInstance(
            lookup().lookupClass().getClassLoader(),
            new Class[]returningWorkClass,
            (proxy, method, args) -> 
                if (method.equals(executeMethod)) 
                    final Connection connection = (Connection) args[0];
                    return function.apply(connection);
                
                return null;
            );
    final Method doReturningWorkMethod = sessionClass.getMethod(
            "doReturningWork", returningWorkClass);
    return (R) doReturningWorkMethod.invoke(session, workProxy);
 catch (final ReflectiveOperationException roe) 
    logger.log(Level.FINE, roe, () -> "failed to work with hibernate");

OpenJPA

Runtime Access to DataSource OPENJPA-1803 Unwrap EntityManager to Connection

我不确定 OpenJPA 是否已经提供了一种使用 unwrap(Connection.class) 的方式,但可以通过上述链接之一中描述的方式来完成。

目前还不清楚关闭连接的责任。文件(上述链接之一)似乎说得很清楚,但我的英语不太好。

try 
    final Class<?> k = Class.forName(
            "org.apache.openjpa.persistence.OpenJPAEntityManager");
    if (k.isInstance(manager)) 
        final Method m = k.getMethod("getConnection");
        try 
            try (Connection c = (Connection) m.invoke(manager)) 
                return function.apply(c);
            
         catch (final SQLException sqle) 
            logger.log(FINE, sqle, () -> "failed to work with openjpa");
        
    
 catch (final ReflectiveOperationException roe) 
    logger.log(Level.FINE, roe, () -> "failed to work with openjpa");

【讨论】:

【参考方案8】:

Hibernate 在内部使用 ConnectionProvider 来获取连接。来自休眠 javadoc:

ConnectionProvider 接口不打算公开给应用程序。相反,Hibernate 在内部使用它来获取连接。

解决这个问题的更优雅的方法是自己创建一个数据库连接池,然后从那里手动连接到 hibernate 和您的旧工具。

【讨论】:

但它暴露给应用程序......(:我认为这可能是答案。 如果您使用 OpenEntityManagerInViewFilter 或相关类,请谨慎使用此方法。要求多个连接来服务一个请求可能会在错误的条件下陷入死锁。【参考方案9】:

我今天遇到了这个问题,这是我所做的,对我有用:

   EntityManagerFactory emf = Persistence.createEntityManagerFactory("DAOMANAGER");
   EntityManagerem = emf.createEntityManager();

   org.hibernate.Session session = ((EntityManagerImpl) em).getSession();
   java.sql.Connection connectionObj = session.connection();

虽然不是最好的方法,但可以做到。

【讨论】:

connection() 方法已弃用!【参考方案10】:

以下是对我有用的代码。我们使用 jpa 1.0,Apache openjpa 实现。

import java.sql.Connection;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAPersistence;

public final class MsSqlDaoFactory 


       public static final Connection getConnection(final EntityManager entityManager) 
              OpenJPAEntityManager openJPAEntityManager = OpenJPAPersistence.cast(entityManager);
              Connection connection = (Connection) openJPAEntityManager.getConnection();
              return connection;

        


【讨论】:

【参考方案11】:

我正在使用旧版本的 Hibernate (3.3.0) 和最新版本的 OpenEJB (4.6.0)。我的解决方案是:

EntityManagerImpl entityManager = (EntityManagerImpl)em.getDelegate();
Session session = entityManager.getSession();
Connection connection = session.connection();
Statement statement = null;
try 
    statement = connection.createStatement();
    statement.execute(sql);
    connection.commit();
 catch (SQLException e) 
    throw new RuntimeException(e);

在那之后我有一个错误:

Commit can not be set while enrolled in a transaction

因为上面的代码在 EJB 控制器中(您不能在事务中使用commit)。我用@TransactionAttribute(value = TransactionAttributeType.NOT_SUPPORTED) 注释了该方法,问题就消失了。

【讨论】:

【参考方案12】:

根据 Dominik 的回答,这是一个与 Hibernate 4 一起使用的代码 sn-p

Connection getConnection() 
    Session session = entityManager.unwrap(Session.class);
    MyWork myWork = new MyWork();
    session.doWork(myWork);
    return myWork.getConnection();


private static class MyWork implements Work 

    Connection conn;

    @Override
    public void execute(Connection arg0) throws SQLException 
        this.conn = arg0;
    

    Connection getConnection() 
        return conn;
    


【讨论】:

execute 终止后连接会发生什么? 这只是获取连接的引用,使用getConnection后可以自定义jdbc代码然后关闭【参考方案13】:

我对 Spring Boot 有点陌生,我还需要 Connection 对象将其发送到 Jasperreport,在尝试了这篇文章中的不同答案之后,这仅对我有用,并且,我希望它可以帮助那些被困在这一点上的人。

@Repository
public class GenericRepository 

private final EntityManager entityManager;

@Autowired
public GenericRepository(EntityManager entityManager, DataSource dataSource) 
    this.entityManager = entityManager;


public Connection getConnection() throws SQLException 
    Map<String, Object> properties = entityManager.getEntityManagerFactory().getProperties();
    HikariDataSource dataSource = (HikariDataSource) properties.get("javax.persistence.nonJtaDataSource");
    return dataSource.getConnection();


【讨论】:

以上是关于在纯 JPA 设置中获取数据库连接的主要内容,如果未能解决你的问题,请参考以下文章

onetomany 单向与使用 jpa 的可连接设置

试图在我的代码中找到数据库连接泄漏,使用 Spring / JPA / Hikari

spring boot JPA 和 数据库连接

使用 JPQL/HQL 在 JPA 中订购连接获取的集合

如何在 JPA 中连接多个数据库?

使用具有一对多关系的 JPA 映射实体,如何添加连接条件