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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

The Little MLer

The Little MLer

Matthias Felleisen、Daniel P. Friedman、Duane Bibby、Robin Milner / The MIT Press / 1998-2-19 / USD 34.00

The book, written in the style of The Little Schemer, introduces instructors, students, and practicioners to type-directed functional programming. It covers basic types, quickly moves into datatypes, ......一起来看看 《The Little MLer》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具