Spring 配置 FAQ

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 doExecute(HibernateCallback 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 配置文件中设置事务管理


    
        
    

然后有两种方式让 sava() 方法在事务管理中进行:

1. 在 Spring 配置文件中设置 TransactionProxyFactoryBean

    
        
        
            
                PROPAGATION_REQUIRED
            
        
        
            
                
            
        
    

注意!



只能二选一。建议使用 transactionAttributes,因为这样配置起来更简洁、方便。另外,

`

必须设置,否则会报错。其目的是为需要使用事务的类声明匿名类。需要把所有用到事务的类都写进去。其他属性详见 Spring 文档。

2. 使用 Spring 注解

在 Spring 配置文件中添加


其作用是开启事务注解。然后在 DAO 层的具体实现类或方法的上一行添加

@Transactional (rollbackFor = Exception.class)

即可。例如:

@Transactional (rollbackFor = Exception.class)
public class BaseDaoHibernateImpl extends HibernateDaoSupport implements BaseDAO {
...
}

添加具体实现类的 Bean

在 Spring 中添加 DAO 层的具体实现类,例如:


    

然后在 Java 代码中获取这个 bean。具体获取可以按照自己的项目来做。这里给个参考例子:

(BaseDAO) applicationContext.getBean ("dao");

结语

Spring 管理事务是非常好用的。但是所有和事务有关的 bean 必须用 Spring 来声明,不能用 new 来声明。否则 new 出来的对象用不了 Spring 中的各种配置。

参考文献

  1. 浅淡Write operations not allowed异常及Spring编程式事务管理, http://www.voidcn.com/article/p-ajtcxbco-beu.html

你可能感兴趣的:(Spring 配置 FAQ)