У нас есть приложение JPA (с использованием hibernate), и нам нужно передать вызов устаревшему инструменту отчетности, которому необходимо подключение базы данных JDBC в качестве параметра. Есть ли простой способ получить доступ к соединению JDBC, установленному в спящем режиме?
Получение соединения с базой данных в чистой настройке JPA
Ответ 1
Если вы хотите получить это соединение, неясно. Одной из возможностей было бы получить его из базового Hibernate Session
, используемого EntityManager
. С JPA 1.0 вам нужно будет сделать что-то вроде этого:
Session session = (Session)em.getDelegate();
Connection conn = session.connection();
Обратите внимание, что getDelegate()
не является переносимым, результат этого метода специфичен для реализации: приведенный выше код работает в JBoss, для GlassFish вам придется его адаптировать - посмотрите Будьте осторожны при использовании EntityManager.getDelegate().
В JPA 2.0 все немного лучше, и вы можете сделать следующее:
Connection conn = em.unwrap(Session.class).connection();
Если вы работаете внутри контейнера, вы также можете выполнить поиск по настроенному DataSource
.
Ответ 2
В соответствии с hibernate
docs здесь,
Подключение()
Устаревшие. (планируется удалить в 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
}
});
Ответ 3
Если вы используете JAVA EE 5.0, лучший способ сделать это - использовать аннотацию @Resource для вставки источника данных в атрибут класса (например, EJB) для хранения ресурса источника данных (например, Oracle datasource) для устаревшего средства отчетности, таким образом:
@Resource(mappedName="jdbc:/OracleDefaultDS") DataSource datasource;
Позже вы можете получить соединение и передать его в устаревший инструмент отчетности таким образом:
Connection conn = dataSource.getConnection();
Ответ 4
если вы используете EclipseLink: Вы должны быть в транзакции JPA для доступа к соединению
entityManager.getTransaction().begin();
java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class);
...
entityManager.getTransaction().commit();
Ответ 5
Так как код, предложенный @Pascal, устарел, как упоминалось в @Jacob, я нашел этот другой способ, который работает для меня.
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();
Ответ 6
Спящий режим 4/5:
Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> doSomeStuffWith(connection));
Ответ 7
Слово "чистый" не соответствует слову "спящий".
Я делюсь своими кодами.
Допустим, мы можем определить метод использования Connection
, полученный из EntityManager
.
static <R> applyConnection(final EntityManager manager,
final Function<Connection, R> function) {
if (manager == null) {
throw new NullPointerException("manager is null");
}
if (function == null) {
throw new NullPointerException("function is null");
}
// we gonna fill here up
throw new RuntimeException("failed to work with a connection");
}
EclipseLink
Это довольно просто, как описано в ссылке выше.
- Обратите внимание, что
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");
}
Hibernate
В основном это должно быть сделано с помощью следующих кодов.
// 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);
}
});
Что ж, нам (по крайней мере, мне) может не потребоваться никаких зависимостей от поставщика. Прокси приходит на помощь.
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
Я не уверен, что 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");
}
ПРИЛОЖЕНИЕ
static <U, R> R applyConnection(
final EntityManager manager,
final BiFunction<Connection, U, R> function, final U u) {
if (manager == null) {
throw new NullPointerException("manager is null");
}
if (function == null) {
throw new NullPointerException("function is null");
}
return applyConnection(manager, t -> function.apply(t, u));
}
static void acceptConnection(
final EntityManager manager, final Consumer<Connection> consumer) {
if (manager == null) {
throw new NullPointerException("manager is null");
}
if (consumer == null) {
throw new NullPointerException("consumer is null");
}
applyConnection(
manager,
t -> {
consumer.accept(t);
return null;
}
);
}
static <U> void acceptConnection(
final EntityManager manager,
final BiConsumer<Connection, U> consumer, final U u) {
if (manager == null) {
throw new NullPointerException("manager is null");
}
if (consumer == null) {
throw new NullPointerException("consumer is null");
}
acceptConnection(manager, t -> consumer.accept(t, u));
}
Ответ 8
Hibernate использует ConnectionProvider для получения соединений. Из спящего режима javadoc:
Интерфейс ConnectionProvider не предназначен для работы с приложением. Вместо этого он используется внутри Hibernate для получения соединений.
Более элегантным способом решения этой проблемы было бы создать пул соединений с базой данных и вручную подключиться к спящему и вашему устаревшему инструменту.
Ответ 9
Я столкнулся с этой проблемой сегодня, и это был трюк, который я сделал, который работал у меня:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("DAOMANAGER");
EntityManagerem = emf.createEntityManager();
org.hibernate.Session session = ((EntityManagerImpl) em).getSession();
java.sql.Connection connectionObj = session.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
Вот фрагмент кода, который работает с Hibernate 4 на основе ответа Dominik
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;
}
}