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

複製代碼
相關文章
相關標籤/搜索