以前咱们使用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
复制代码