Spring: @Transactional с readonly=true, не вызывать conn.setReadOnly(true)
Я аннотировал свои методы обслуживания @Transactional
readonly=true
,
Это с той весны /hibernate не вызывать метод setReadonly драйвера соединения jdbc. А что я могу сделать?
Из-за этого я буду использовать репликацию master-slave, а пул jdbc использует флаг readonly для соединения, чтобы направить запрос к master или slave.
2 ответа
Во-первых, вы должны установить флаг readOnly только для соединения JDBC, когда режим транзакции PU - RESOURCE_LOCAL. Если это JTA, то вы не должны изменять этот параметр, так как вы не получите один и тот же экземпляр соединения jdbc для каждого вызова jdbc внутри транзакции (JTA - и не Hibernate - обеспечит поведение транзакций). Когда он ЛОКАЛЬНЫЙ, Hibernate открывает соединение jdbc при первом обращении к нему и сохраняет его на время транзакции.
1. JPA
Если вы используете JPA с Hibernate в качестве провайдера, вы можете добавить это дополнительное поведение, предоставив собственную реализацию JpaDialect для определения EMF. При использовании Hibernate вы обычно вводите HibernateJpaDialect.
Интерфейс JpaDialect имеет getJdbcConnection(em, readOnly)
метод, который возвращает дескриптор фактического соединения JDBC. Этот метод вызывается JpaTransactionManager, когда транзакция начинается. По умолчанию HibernateJpaDialect не изменяет параметр readOnly в возвращаемом соединении из-за этой двойственности JTA/RESOURCE_LOCAL, но вы можете сделать это, только если вы выполняете только локальные транзакции.
Вот реализация такого JpaDialect, который достигает вашей цели:
ResourceLocalReadOnlyAwareHibernateJpaDialect
public class ResourceLocalReadOnlyAwareHibernateJpaDialect extends HibernateJpaDialect {
public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) throws PersistenceException, SQLException {
Session session = getSession(entityManager);
return new HibernateReadOnlyAwareConnectionHandle(session, readOnly);
}
// this is similar to spring's HibernateJpaDialect own internal class,
// except for the readonly flags.
private static class HibernateReadOnlyAwareConnectionHandleimplements ConnectionHandle {
private final Session session;
private final boolean readOnly;
private static volatile Method connectionMethod;
public HibernateConnectionHandle(Session session, boolean readOnly) {
this.session = session;
this.readOnly = readOnly;
}
public Connection getConnection() {
try {
if (connectionMethod == null) {
// reflective lookup to bridge between Hibernate 3.x and 4.x
connectionMethod = this.session.getClass().getMethod("connection");
}
Connection con = (Connection) ReflectionUtils.invokeMethod(connectionMethod, this.session);
con.setReadOnly(this.readOnly);
return con;
} catch (NoSuchMethodException ex) {
throw new IllegalStateException("Cannot find connection() method on Hibernate session", ex);
}
}
public void releaseConnection(Connection con) { // #1
con.setReadOnly(false);
JdbcUtils.closeConnection(con);
}
}
}
Примечание #1: Сбрасывает флаг readOnly в false перед закрытием соединения (на самом деле не реально connection.close()
звоните, но просто освобождая соединение с пулом). Не совсем уверен, что вызывает этот вызов метода, но выглядит вполне законным, чтобы сбросить флаг readOnly в том же классе, где он был изменен.
2. Чистая Спящая
Во-первых, обеспечить HibernateTransactionManager.prepareConnection
остается верным.
Тогда я не уверен, что делать. Вы должны отладить в Spring HibernateTransactionManager.isSameConnectionForEntireSession()
: если метод возвращает true, будет вызвано connection.setReadOnly(), поэтому все в порядке.
Если нет, вы можете изменить настройку connectionReleaseMode в Hibernate на ON_CLOSE (свойство hibernate hibernate.transaction.auto_close_session=true
, который был по умолчанию до Hibernate 3.1), или перезаписать HibernateTransactionManager.isSameConnectionForEntireSession()
чтобы всегда возвращать истину (что считается безопасным в отношении комментариев HibernateTransactionManager). Оба являются "продвинутым тюнингом", но должны быть безопасны AFAIK. На самом деле, я думаю, что HibernateTransactionManager.isSameConnectionForEntireSession()
следует изменить, чтобы он возвращал значение true для режимов выпуска ON_CLOSE и AFTER_TRANSACTION: в отношении HibernateTransactionManager очистка происходит в любом случае после завершения транзакции, таким образом, не изменяя поведение Hibernate.
Здесь упоминаются два решения, которые стоит изучить: http://www.dragishak.com/?p=307
- Использование AOP для установки соединения JDBC на readOnly. Это фокус сообщения в блоге.
- Использование хуков в пуле соединений для выбора другого соединения на основе
TransactionSynchronizationManager.isCurrentTransactionReadOnly()
, Это зависит от того, какую реализацию пула соединений вы используете (например, BoneCP, c3p0, пока не уверены, поддерживается ли она в DBCP). Смотрите раздел комментариев по ссылке выше.