在纯 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 设置中获取数据库连接的主要内容,如果未能解决你的问题,请参考以下文章