Spring 配置 FAQ
无法保存对象
错误提示: 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 的 save() 方法中的 checkWriteOperationAllowed() 方法:
protected void checkWriteOperationAllowed(Session session) throws InvalidDataAccessApiUsageException {
if (isCheckWriteOperations() && session.getFlushMode().lessThan(FlushMode.COMMIT)) {
throw new 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.");
}
}
表面上看是因为 Session 设置了 FlushMode.MANUAL 或者事务定义了 readOnly = true,导致该对象不能写入数据库。但实际上是因为该对象在没有事务管理的情况下进行写入,才导致无法保存,出现上述错误。
详细原因
查看 HibernateTemplate 中 save() 的源码,发现其调用顺序为: save() --> executeWithNativeSession() --> doExecute()。查看 doExecute() 源码如下:
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 {
/* 如果没有使用 TransactionManager(包括 HibernateTransactionManager),
* 则 session 的值仍然为 null。*/
session = getSessionFactory().getCurrentSession();
}
catch (HibernateException ex) {
logger.debug("Could not retrieve pre-bound Hibernate session", ex);
}
/* 有上述可知 session == null,因此,在此处 Spring 调用 Hibernate 的 API 新建一个 session。
* 新建的 Session 的 FlushMode 是 MANUAL,只读不可写。
*/
if (session == null) {
session = getSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
isNew = true;
}
try {
enableFilters(session);
Session sessionToExpose =
(enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session));
/* 如果开启了事务,则跳过 if(session == null) 执行到这里
* doInHibernate 在 save() 方法中,以匿名内部类的方式定义。
*/
return action.doInHibernate(sessionToExpose);
}
catch (HibernateException ex) {
throw SessionFactoryUtils.convertHibernateAccessException(ex);
}
catch (RuntimeException ex) {
// Callback code threw application exception...
throw ex;
}
finally {
if (isNew) {
SessionFactoryUtils.closeSession(session);
}
else {
disableFilters(session);
}
}
}
解决方案
首先,要配置 Spring 的事务管理。然后,必须用 Spring 中的 Bean 来注入到具体实现类中。
在 Spring 配置文件中设置事务管理
<!-- 配置 Hibernate 事务管理 -->
<bean id = "hibernateTransactionManager"
class = "org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name = "sessionFactory" ref = "mysqlSessionFactory" />
</bean>
然后有两种方式让 sava() 方法在事务管理中进行:
1. 在 Spring 配置文件中设置 TransactionProxyFactoryBean
<!-- 配置 事务代理工厂Bean -->
<bean id = "hibernateTransactionProxyFactoryBean"
class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name = "transactionManager" ref = "hibernateTransactionManager" />
<property name = "transactionAttributeSource">
<props>
<prop key = "*">PROPAGATION_REQUIRED</prop>
</props>
</property>
<property name = "target">
<bean class = "XXX.dao.impl.BaseDaoHibernateImpl">
<property name = "sessionFactory" ref = "mysqlSessionFactory" />
</bean>
</property>
</bean>
注意!
<property name = "transactionAttributes">
和
<property name = "transactionAttributeSource">
只能二选一。建议使用 transactionAttributes
,因为这样配置起来更简洁、方便。另外,
<property name = "target">`
必须设置,否则会报错。其目的是为需要使用事务的类声明匿名类。需要把所有用到事务的类都写进去。其他属性详见 Spring 文档。
2. 使用 Spring 注解
在 Spring 配置文件中添加
<tx:annotation-driven transaction-manager = "hibernateTransactionManager" />
其作用是开启事务注解。然后在 DAO 层的具体实现类或方法的上一行添加
@Transactional (rollbackFor = Exception.class)
即可。例如:
@Transactional (rollbackFor = Exception.class)
public class BaseDaoHibernateImpl<T> extends HibernateDaoSupport implements BaseDAO<T> {
...
}
添加具体实现类的 Bean
在 Spring 中添加 DAO 层的具体实现类,例如:
<bean id="dao" class = "XXX.impl.BaseDaoHibernateImpl">
<property name = "sessionFactory" ref = "mysqlSessionFactory" />
</bean>
然后在 Java 代码中获取这个 bean。具体获取可以按照自己的项目来做。这里给个参考例子:
(BaseDAO) applicationContext.getBean ("dao");
结语
Spring 管理事务是非常好用的。但是所有和事务有关的 bean 必须用 Spring 来声明,不能用 new 来声明。否则 new 出来的对象用不了 Spring 中的各种配置。
参考文献
- 浅淡Write operations not allowed异常及Spring编程式事务管理, http://www.voidcn.com/article/p-ajtcxbco-beu.html
网友评论