之前我们使用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自增,其次是新表,所以也不可能出现重复数据,然后我是单表,不存在映射关系。
排除以上可能性问题后,没办法,只有打印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,问题就出在这里了。
好了,问题出现原因我们也找到了,现在来想想解决办法,无非有两种:
第一种方式直接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 复制代码