美文网首页
Spring 配置 FAQ

Spring 配置 FAQ

作者: zephyrous | 来源:发表于2018-01-12 13:33 被阅读0次

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 中的各种配置。

参考文献

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

相关文章

  • Spring 配置 FAQ

    Spring 配置 FAQ 无法保存对象 错误提示: Write operations are not allow...

  • Spring Boot2使用小记

    FAQ 如何快速搭建一个spring boot项目? 使用Spring Initializr可以快速获取一个spr...

  • mysql 实现主从同步

    环境介绍 主环境配置 从环境配置 到此可以进行主从测试了。 提示: FAQ

  • Spring的java配置方式

    Spring的java配置方式 一、Spring的java配置方式 Java配置是Spring4.x推荐的配置方式...

  • LuaIde 文档导航页

    VsCode使用基础 LuaIde 配置项 LuaIde 标签使用 api 提示生成 FAQ 购买Luaide 视...

  • SSM框架集成

    一、核心配置 配置spring-context.xml 配置spring-mvc.xml 配置web.xml 配置...

  • 第一章 Spring基础

    第一章 Spring基础 1.3 Spring 基础配置 1.3.2 Java配置 Java配置是Spring4....

  • Spring Mybatis初始化

    配置 欲在Spring中使用Mybatis,需要引入 在Spring配置文件中添加Spring配置 dataSou...

  • Spring学习笔记

    Spring基于XML方式的容器配置 Spring基于注解方式的容器配置 Spring基于Java方式的容器配置

  • 第1章: Spring 基础

    第1章:Spring 基础 spring 简史 xml配置spring1.x,主要配置各种Bean 注解配置spr...

网友评论

      本文标题:Spring 配置 FAQ

      本文链接:https://www.haomeiwen.com/subject/ujhfoxtx.html