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

以前咱们使用hibernate3的时候采用xml式配置,以下所示:php

<?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;java


后面为了适应注解因此升级了hibernate4,而且改成了注解式配置实例,以下:sql

@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 updatesession

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

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

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

排除以上可能性问题后,没办法,只有打印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出来的这个对象是游离态了。没办法,只有跟代码了。fetch


首先咱们看看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

复制代码
相关文章
相关标签/搜索