hibernate 使用saveOrUpde 报 Batch update returned unexpected row count from update

栏目: Hibernate · 发布时间: 6年前

内容简介:之前我们使用hibernate3的时候采用xml式配置,如下所示:从上面的xml配置文件中我们可以看出,我们的主键使用的是由程序控制的主键,也就是说,我们在保存Person时,必须手动调用setter给ID设置一下ID;后面为了适应注解所以升级了hibernate4,并且改为了注解式配置实例,如下:

之前我们使用hibernate3的时候采用xml式配置,如下所示:

<?xml version="1.0" encoding="gb2312"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.xx.xx.beans">
    <class name="Person" table="person">
        <id column="id" name="id" type="java.lang.Long"><generator class="assigned" /></id>
        <property column="name" length="30" name="name" not-null="true" type="java.lang.String" />
    </class>
</hibernate-mapping>

复制代码

从上面的xml配置文件中我们可以看出,我们的主键使用的是由程序控制的主键,也就是说,我们在保存Person时,必须手动调用setter给ID设置一下ID;

后面为了适应注解所以升级了hibernate4,并且改为了注解式配置实例,如下:

@Entity
@Table(name = "person")
public class Person{
    
    @Id
    @GenericGenerator(name = "idGenerator", strategy = "assigned")
    @GeneratedValue(generator = "idGenerator")
    private Long id;
    private String name;

}
复制代码

从代码上我们可以看到,为了满足手动设置ID,我们定义了一个GenericGenerator,strategy为assigned,关于其他的strategy类型,大家可以在网上查阅资料。

这么配置好后,理论上是没有什么问题了,但是当我们调用session.saveOrUpdate的时候,会报出标题所示的错误:

Batch update returned unexpected row count from update

之前也是一头雾水,就到网上查阅了各种资料,基本问题有一下几种:

  • 给ID配置了@GeneratedValue但是数据库中设置为ID自增
  • 数据库中存在重复数据
  • 一对多,多对多映射保存时会出现此异常

关于以上可能性问题我基本一一排除,因为我是手动建表,所以不可能出现ID自增,其次是新表,所以也不可能出现重复数据,然后我是单表,不存在映射关系。

排除以上可能性问题后,没办法,只有打印hibernate的查询语句,通过打印hibernate的 sql 语句发现,我调用saveOrUpdate时,sql为update语句。这就很令人费解,我这个对象基本是如下这种操作:

// service
public class PersonServiceImpl implements IPersonService{

    @Autowired
    private IPersonDao personDao;
    
    void savePerson(){
        // 此处为其他代码
        Person p = new Person();
        p.setId(1L);
        personDao.saveOrUpdate(p)
    }
    
}

// dao
public class PersonDaoImpl implements IPersonDao{
    
    void saveOrUpdate(Person person){
        // 伪代码,我们采用hibernateTemplate
        this.hibernateTemplate.saveOrUpdate(person).
    }
    
}

复制代码

从代码上看,是不可能会出现update语句的,出现update语句只有一种可能,那就是hibernate认为我new出来的这个对象是游离态了。没办法,只有跟代码了。

首先我们看看hibernateTemplate的saveOrUpdate:

public void saveOrUpdate(final Object entity) throws DataAccessException {
    this.executeWithNativeSession(new HibernateCallback<Object>() {
        public Object doInHibernate(Session session) throws HibernateException {
            HibernateTemplate.this.checkWriteOperationAllowed(session);
            session.saveOrUpdate(entity);
            return null;
        }
    });
}
复制代码

我们发现实际上也是调用的session的saveOrUpdate,所以通过跟session#saveOrUpdate,发现session的saveOrUpdate是通过类似监听的机制来实现的:

public final class SessionImpl extends AbstractSessionImpl implements EventSource {
    
    private void fireSaveOrUpdate(SaveOrUpdateEvent event) {
        this.errorIfClosed();
        this.checkTransactionSynchStatus();
        this.checkNoUnresolvedActionsBeforeOperation();
        Iterator i$ = this.listeners(EventType.SAVE_UPDATE).iterator();

        while(i$.hasNext()) {
            SaveOrUpdateEventListener listener = (SaveOrUpdateEventListener)i$.next();
            listener.onSaveOrUpdate(event);
        }

        this.checkNoUnresolvedActionsAfterOperation();
    }
}

复制代码

继续深入,我们找到实际会触发此错误的地方:

package org.hibernate.event.internal;

public class DefaultSaveOrUpdateEventListener extends AbstractSaveEventListener implements SaveOrUpdateEventListener {
    
    protected Serializable performSaveOrUpdate(SaveOrUpdateEvent event) {
    	// 此处为查询我们保存的对象的状态的
    	EntityState entityState = getEntityState(
    	    event.getEntity(),
    	    event.getEntityName(),
    	    event.getEntry(),		
    	    event.getSession()
    	);
    
    	switch ( entityState ) {
    	    case DETACHED:
    	        // 游离态,执行update语句
    	        entityIsDetached( event );
    	        return null; 
    	    case PERSISTENT:
    	        // 持久态,不会执行任何语句
    	        return entityIsPersistent( event );
    	    default: //TRANSIENT or DELETED
    	        // 临时态,会执行insert语句
    	        return entityIsTransient( event );
    	}
    }
}
复制代码

通过阅读以上代码,我们知道问题出在获取对象状态的地方,及:getEntityState。让我们继续深入挖掘:

package org.hibernate.event.internal;

public abstract class AbstractSaveEventListener extends AbstractReassociateEventListener {
    protected EntityState getEntityState(
			Object entity,
			String entityName,
			EntityEntry entry, //pass this as an argument only to avoid double looking
			SessionImplementor source) {

		final boolean traceEnabled = LOG.isTraceEnabled();
		if ( entry != null ) { // the object is persistent

			//the entity is associated with the session, so check its status
			if ( entry.getStatus() != Status.DELETED ) {
				// do nothing for persistent instances
				if ( traceEnabled ) {
					LOG.tracev( "Persistent instance of: {0}", getLoggableName( entityName, entity ) );
				}
				return EntityState.PERSISTENT;
			}
			// ie. e.status==DELETED
			if ( traceEnabled ) {
				LOG.tracev( "Deleted instance of: {0}", getLoggableName( entityName, entity ) );
			}
			return EntityState.DELETED;
		}
		// the object is transient or detached

		// the entity is not associated with the session, so
		// try interceptor and unsaved-value

		if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source ) ) {
			if ( traceEnabled ) {
				LOG.tracev( "Transient instance of: {0}", getLoggableName( entityName, entity ) );
			}
			return EntityState.TRANSIENT;
		}
		if ( traceEnabled ) {
			LOG.tracev( "Detached instance of: {0}", getLoggableName( entityName, entity ) );
		}
		return EntityState.DETACHED;
	}
}
复制代码

通过跟踪代码,发现上面判断类型的代码段一个都没进,默认就返回游离态(DETACHED)。配合前面的代码大家就知道肯定就会执行update语句,但是实际上我们数据库又没有这条数据,自然就会报上面的错误了。

由于我们知道我们的对象是属于临时态(EntityState.TRANSIENT),所以我们来研究 ForeignKeys.isTransient 这个方法:

package org.hibernate.engine.internal;

public final class ForeignKeys {

public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
		if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
			// an unfetched association can only point to
			// an entity that already exists in the db
			return false;
		}

		// 通过拦截器检查
		Boolean isUnsaved = session.getInterceptor().isTransient( entity );
		if ( isUnsaved != null ) {
			return isUnsaved;
		}

		// 通过持久程序检查是否没有存储
		final EntityPersister persister = session.getEntityPersister( entityName, entity );
		isUnsaved = persister.isTransient( entity, session );
		if ( isUnsaved != null ) {
			return isUnsaved;
		}

		// we use the assumed value, if there is one, to avoid hitting
		// the database
		if ( assumed != null ) {
			return assumed;
		}

		// 获取数据库快照
		final Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
				persister.getIdentifier( entity, session ),
				persister
		);
		return snapshot == null;

	}
    
}

复制代码

通过对以上代码的跟踪,发现 persister.isTransient( entity, session ); 时返回了false,意思是持久程序已经判断当前这个对象是已经存在了,那么这个地方就存在问题,我们继续深入:

package org.hibernate.persister.entity;

public abstract class AbstractEntityPersister
		implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable,
		SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable {
		
		
		
		public Boolean isTransient(Object entity, SessionImplementor session) throws HibernateException {
		// 获取待保存对象的ID
		final Serializable id;
		if ( canExtractIdOutOfEntity() ) {
			id = getIdentifier( entity, session );
		}
		else {
			id = null;
		}
		// 如果id为空,默认为临时态
		if ( id == null ) {
			return Boolean.TRUE;
		}

		// 检查版本号,即乐观锁
		final Object version = getVersion( entity );
		if ( isVersioned() ) {
			// let this take precedence if defined, since it works for
			// assigned identifiers
			Boolean result = entityMetamodel.getVersionProperty()
					.getUnsavedValue().isUnsaved( version );
			if ( result != null ) {
				return result;
			}
		}

		// 检查ID是否为临时态的值
		Boolean result = entityMetamodel.getIdentifierProperty()
				.getUnsavedValue().isUnsaved( id );
		if ( result != null ) {
			return result;
		}

		// 检查是否存在二级缓存中
		if ( session.getCacheMode().isGetEnabled() && hasCache() ) {
			final CacheKey ck = session.generateCacheKey( id, getIdentifierType(), getRootEntityName() );
			final Object ce = CacheHelper.fromSharedCache( session, ck, getCacheAccessStrategy() );
			if ( ce != null ) {
				return Boolean.FALSE;
			}
		}

		return null;
	}
		
}

复制代码

又跟踪以上代码发现 entityMetamodel.getIdentifierProperty().getUnsavedValue().isUnsaved( id ); 返回了false。这个方法是干什么用的呢,他是获取我们ID的定义属性,即我们配置的一些@GeneratedValue等, getUnsavedValue 方法是获取未保存的时候的ID包装,然后通过 isUnsaved 方法来对比是否相同。

package org.hibernate.engine.spi;

public class IdentifierValue implements UnsavedValueStrategy {

    /**
	 * 总是假设所有的都是新实例
	 */
	public static final IdentifierValue ANY = new IdentifierValue() {
		@Override
		public final Boolean isUnsaved(Object id) {
			LOG.trace( "ID unsaved-value strategy ANY" );
			return Boolean.TRUE;
		}

		@Override
		public Serializable getDefaultValue(Object currentValue) {
			return (Serializable) currentValue;
		}

		@Override
		public String toString() {
			return "SAVE_ANY";
		}
	};

	/**
	 * 总是假设所有的都不是新实例
	 */
	public static final IdentifierValue NONE = new IdentifierValue() {
		@Override
		public final Boolean isUnsaved(Object id) {
			LOG.trace( "ID unsaved-value strategy NONE" );
			return Boolean.FALSE;
		}

		@Override
		public Serializable getDefaultValue(Object currentValue) {
			return (Serializable) currentValue;
		}

		@Override
		public String toString() {
			return "SAVE_NONE";
		}
	};

	/**
	 * 假设ID为空是,该对象为新实例
	 */
	public static final IdentifierValue NULL = new IdentifierValue() {
		@Override
		public final Boolean isUnsaved(Object id) {
			LOG.trace( "ID unsaved-value strategy NULL" );
			return id == null;
		}

		@Override
		public Serializable getDefaultValue(Object currentValue) {
			return null;
		}

		@Override
		public String toString() {
			return "SAVE_NULL";
		}
	};

	/**
	 * 不假设
	 */
	public static final IdentifierValue UNDEFINED = new IdentifierValue() {
		@Override
		public final Boolean isUnsaved(Object id) {
			LOG.trace( "ID unsaved-value strategy UNDEFINED" );
			return null;
		}

		@Override
		public Serializable getDefaultValue(Object currentValue) {
			return null;
		}

		@Override
		public String toString() {
			return "UNDEFINED";
		}
	};

    @Override
	public Boolean isUnsaved(Object id) {
		LOG.tracev( "ID unsaved-value: {0}", value );
		return id == null || id.equals( value );
	}

}
复制代码

这里我们发现,value为null,但是我们的待保存的对象的ID不为null,肯定就会返回false,问题就出在这里了。

好了,问题出现原因我们也找到了,现在来想想解决办法,无非有两种:

  • 设置空对象的value
  • 取消IdentifierProperty的配置,让getIdentifierValue时get到UNDEFINED类型的IdentifierValue

第一种方式直接pass,因为我们系统是业务系统,基本都需要预先设置好ID。那这个空对象的ID值就没啥用了。综上所述,所以只有取消掉IdentifierProperty配置,即取消掉bean上的@GenericGenerator和@GeneratedValue:

@Entity
@Table(name = "person")
public class Person{
    
    @Id
    private Long id;
    private String name;

}

复制代码

OK,问题解决。

附上网上搜集的设置unsaved-value的方式,(未测试)

@Id 
    @GeneratedValue(generator="idGenerator")  
    @GenericGenerator(name="idGenerator", strategy="assigned", parameters = {
            @Parameter(name = "unsaved-value" , value = "-1")
    })  
    private Long id

复制代码

以上所述就是小编给大家介绍的《hibernate 使用saveOrUpde 报 Batch update returned unexpected row count from update》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

点击的奥秘:运用说服心理术提升在线影响力(全彩)

点击的奥秘:运用说服心理术提升在线影响力(全彩)

Nathalie Nahai(娜塔莉.纳海) / 陈旭 / 电子工业出版社 / 2014-9-1 / 75.00元

用户的每一次点击,不管是在虚拟商店购物,还是在浏览企业网站,或是漫无目的地把玩手机,都蕴藏着基于心理学的无穷奥秘。《点击的奥秘:运用说服心理术提升在线影响力》作者为全球知名的网络心理学家,其在《点击的奥秘:运用说服心理术提升在线影响力》中将心理学、神经科学及行为经济学巧妙地结合在一起,挖掘和提炼出一套行之有效的网络用户引导策略——既涵盖在线说服最新研究动向,也包括最前沿的科技成果,以及其他诸多惊人......一起来看看 《点击的奥秘:运用说服心理术提升在线影响力(全彩)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具