最近项目把Hibernate和Spring升级到了5,记录出现的常见问题。
在对数据源进行写操作(插入、更新、删除)的时候,遇到这样的报错
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
at org.springframework.orm.hibernate5.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1093)
at org.springframework.orm.hibernate5.HibernateTemplate.lambda$save$11(HibernateTemplate.java:637)
at org.springframework.orm.hibernate5.HibernateTemplate.doExecute(HibernateTemplate.java:383)
at org.springframework.orm.hibernate5.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:349)
at org.springframework.orm.hibernate5.HibernateTemplate.save(HibernateTemplate.java:636)
似乎和FlushMode有关系, 这是升级Spring5以后才遇到的,看来是Spring的实现有所改动。通过追踪代码的方式看看Spring5到底做了什么。
以save操作为例,以HibernateTemplate的save方法为起点
@Override
public Serializable save(final Object entity) throws DataAccessException {
return nonNull(executeWithNativeSession(session -> {
checkWriteOperationAllowed(session);
return session.save(entity);
}));
}
直接返回executeWithNativeSession的返回结果,并创建了一个匿名类实现了回调方法。
进入executeWithNativeSession方法
@Nullable
public <T> T executeWithNativeSession(HibernateCallback<T> action) {
return doExecute(action, true);
}
继续跟踪doExecute
@Nullable
protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Session session = null;
boolean isNew = false;
try {
session = obtainSessionFactory().getCurrentSession();
}
catch (HibernateException ex) {
logger.debug("Could not retrieve pre-bound Hibernate session", ex);
}
if (session == null) {
session = obtainSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
isNew = true;
}
try {
enableFilters(session);
Session sessionToExpose =
(enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
return action.doInHibernate(sessionToExpose);
}
catch (HibernateException ex) {
throw SessionFactoryUtils.convertHibernateAccessException(ex);
}
catch (PersistenceException ex) {
if (ex.getCause() instanceof HibernateException) {
throw SessionFactoryUtils.convertHibernateAccessException((HibernateException) ex.getCause());
}
throw ex;
}
catch (RuntimeException ex) {
// Callback code threw application exception...
throw ex;
}
finally {
if (isNew) {
SessionFactoryUtils.closeSession(session);
}
else {
disableFilters(session);
}
}
}
先进行操作session = obtainSessionFactory().getCurrentSession();获取当前事务的session,如果当前不在事务中,这个操作会抛异常,被捕获后输出日志,代码继续往下走。此时session为null
在session为Null时,执行obtainSessionFactory().openSession()创建新的session,并且把flushMode设置为Manual。就是因为这里导致我们的报错。
注意这个FlushMode.MANUAL,这个模式是只读的,在这个模式的session下对数据源进行写操作,就会出现标题中的Write operations are not allowed in read-only mode (FlushMode.MANUAL)错误。
解决方法就是自己声明事务,替代spring自动生成的事务,并且把readOnly设置为false。
@Transactional(readOnly=false)