Java/Hibernate - 在只读模式下不允许进行写操作

17

最近我一直遇到一个非常烦人的异常,经过在Google和这个论坛上的一些调研后,我仍然没有找到能够解决我的问题的答案。

事情是这样的-有时候,在尝试使用Hibernate更新或创建新对象时,我会收到以下错误:

org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.NEVER/MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
at org.springframework.orm.hibernate3.HibernateTemplate.checkWriteOperationAllowed(HibernateTemplate.java:1186)
at org.springframework.orm.hibernate3.HibernateTemplate$12.doInHibernate(HibernateTemplate.java:696)
at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:419)
at org.springframework.orm.hibernate3.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:374)
at org.springframework.orm.hibernate3.HibernateTemplate.save(HibernateTemplate.java:694)
有时候使用方法getHibernateTemplate().saveOrUpdate(object);更新对象时非常奇怪,它有时可以工作,但有时对于同一个对象,在调用相同的方法时就无法工作,这似乎取决于我首次获取对象的方式。
例如:假设我有一个三个字段的表:id、type和length。如果我通过id获取该对象并更新其长度,则会成功。如果我通过类型获取并更新长度,则不起作用。因此,我一直在尝试避免问题的方法是以不会在后面引起问题的方法获取对象,但是越来越烦人了,而且很难找到一个可行的方法。
同时,现在我在尝试创建一个对象(但只是在一个特定的表中),却出现了异常,并且找不到解决方法。我尝试在事务中添加@Transactional(readOnly = false),但没有改变任何东西,并且显示模式也表示我不是只读的。 有什么建议吗?
编辑7月26日:以下是与Hibernate相关的一些配置。
<property name="hibernateProperties">
    <props>
        <prop key="jdbc.fetch_size">20</prop>
        <prop key="jdbc.batch_size">25</prop>
        <prop key="cglib.use_reflection_optimizer">true</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
        <prop key="connection.autoReconnect">true</prop>
        <prop key="connection.autoReconnectForPools">true</prop>
        <prop key="connection.is-connection-validation-required">true</prop>
    </props>
</property>

另外,如果可以帮助的话

<property name="transactionAttributes">
    <props>
        <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
        <prop key="execute*">PROPAGATION_REQUIRED</prop>
        <prop key="add*">PROPAGATION_REQUIRED</prop>
        <prop key="create*">PROPAGATION_REQUIRED</prop>
        <prop key="update*">PROPAGATION_REQUIRED</prop>
        <prop key="delete*">PROPAGATION_REQUIRED</prop>
    </props>
</property>

8月31日编辑: 我扩展了HibernateDaoSupport的类中,保存对象的相关代码如下:

public void createObject(Object persisObj) {
    getHibernateTemplate().save(persisObj);
}

2
你是否在使用@CacheREAD_ONLY模式?或者在某个方法/类上注释了@Transactional(readOnly = true)?我自己也遇到过这些问题,并发现这两个配置对此错误起了作用。特别是如果你加载一个对象,修改它并再次持久化。我特别不喜欢使用HibernateTemplate,因为它可能会隐藏一些后台配置。如果你贴出一些配置片段,我可能能够帮忙。 - Tarcio Saraiva
感谢T Man抽出时间回复。所以我在整个项目中搜索了@Cache/@Transactional,但没有找到任何相关内容(我也从未将其放入我的项目中)。实际上,我并没有配置hibernate或编写这些方法,但我现在会在帖子中添加我的配置。此外,当创建某些对象时会出现此错误,但不是所有对象都会出现(始终相同),因此它不仅与更新对象有关。 - Guillaume
13个回答

17

当使用Spring的OpenSessionInViewFilter并尝试在Spring管理的事务之外进行持久化操作时,通常会看到该错误消息。该过滤器将会话设置为FlushMode.NEVER/MANUAL(取决于您使用的Spring和Hibernate版本——它们大致相当)。当Spring事务机制开始事务时,它将切换flush模式为“COMMIT”。事务完成后,它将根据需要将其设置回NEVER/MANUAL。如果您绝对确定这不会发生,那么下一个最可能的罪魁祸首是非线程安全使用Session。Hibernate Session必须仅在一个线程中使用。如果它在线程之间交叉,就可能会发生各种混乱。请注意,从Hibernate加载的实体可以持有对加载它的Session的引用,将实体移交给其他线程可能会导致从另一个线程访问Session。


谢谢你的回答。你似乎对这个问题很了解,但是我对你的回答有些困惑。因此,我正在使用Eclipse进行开发,你认为我应该怎么做来检测/纠正这个问题?我能做些什么来尝试修复它? - Guillaume
2
最好的方法可能是在InvalidDataAccessApiUsageException上添加异常断点,然后调试应用程序并使其发生。然后检查堆栈以查看如何到达异常。特别检查会话是如何打开和事务是否已启动的位置。您可以从异常堆栈跟踪中了解其中一些内容,但在调试器中实时查看更好。 - Ryan Stewart
我还是卡在这个问题上。我试着调试了一下,但实际上并不知道该怎么做。我没有看到任何会话或其他东西。有人有建议吗? - Guillaume
即使过了7年,这个答案仍然有帮助。我在持久化数据时遇到了这个问题,方法没有使用@transaction进行注释,即在Spring事务之外。 - Vikas

10

添加

@Transactional

超出您的函数


8

我从视图筛选器更改了单个会话属性。问题已解决:

  <filter>
    <filter-name>hibernateFilter</filter-name>
    <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    <init-param>
      <param-name>singleSession</param-name>
      <param-value>false</param-value>
    </init-param>
  </filter>

4
وˆ‘ه°‌试ن؛†getHibernateTemplate().getSessionFactory().getCurrentSession().setFlushMode(FlushMode.AUTO);,وˆ‘çڑ„é—®é¢که¾—هˆ°ن؛†è§£ه†³... - prem30488

6
我也遇到了这个问题。我需要在Spring的OpenSessionInViewFilter中更改flush模式为手动,突然间我开始收到这个异常。我发现问题出现在没有被标注为@Transactional的方法中,所以我想Spring隐含地将所有数据访问代码在这些方法之外视为只读。标注该方法解决了问题。
另一种方法是调用HibernateTemplate对象上的setCheckWriteOperations方法。

所以我尝试在方法中添加@Transactional(请参见我的编辑帖子获取代码):没有任何改变。然后我尝试在保存之前调用setCheckWriteOperations(false);,但是遇到了异常:“java.sql.SQLException: Connection is read-only。不允许修改数据的查询”。然后我尝试删除 <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> 中的,readOnly 部分(请查看我的第一个编辑),这样就好了!使用此方法,@Transactional 和检查事项都无关紧要,但我想这并不是一个好的解决方法。有什么想法为什么会发生这种情况吗? - Guillaume
我真的一点头绪都没有。你的get*属性不会偶然创建一些新对象吧?比如,向日志数据库表中添加一个条目或类似的操作? - alh84001

5
在应用程序上下文中使用以下bean作为HibernateTemplate。
<bean id="template" class="org.springframework.orm.hibernate4.HibernateTemplate">  
        <property name="sessionFactory" ref="mysessionFactory"></property>
        <property name="checkWriteOperations" value="false"></property>
        </bean>

4

尝试使用这个

hibernateTemplate = new HibernateTemplate(sessionFactory);
hibernateTemplate.setCheckWriteOperations(false);

如果模板没有检查是否使用事务,则错误应该消失。


我尝试了 getHibernateTemplate().getSessionFactory().getCurrentSession().setFlushMode(FlushMode.AUTO);,我的问题得到了解决。 - prem30488

2
下面的代码对我起作用了。
hibernateTemplate = new HibernateTemplate(sessionFactory);
hibernateTemplate.setCheckWriteOperations(false);

2
你可能忘了在你的DAO服务类/方法上添加@Transactional注解,这将指定由Spring管理的事务处理你的数据库操作。

2
  1. 在你的数据库操作方法上方添加@Transactional注解。

  2. 确保你已经使用注解驱动的事务管理器,在applicationContext.xml文件中HibernateTransactionManager配置上方添加<tx:annotation-driven/>标签。


1
Dao/Repository类上添加@Transactional注释。
@Repository
@Transactional
public class XxxRepository {
    
    @Autowired
    private HibernateTemplate hibernateTemplate;
    
    .
    .
    .

}

虽然不确定这是否是最佳解决方案,但对我来说它起作用了。 - Amirhosein Al

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接