以前咱們使用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 update
session
以前也是一頭霧水,就到網上查閱了各類資料,基本問題有一下幾種:app
關於以上可能性問題我基本一一排除,由於我是手動建表,因此不可能出現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,問題就出在這裏了。
好了,問題出現緣由咱們也找到了,如今來想一想解決辦法,無非有兩種:
第一種方式直接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
複製代碼