public class EditPrivilege extends EntityId {
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = REL_EDITPRIVILEGE_ROLE)
private Set editableRoles = new HashSet<>();
@Column
private String modelName;
}
当应用启动,自动建表并导入数据,执行repository.save()方法报detached entity passed to persist: edu.ecnu.yjsy.model.auth.Role
错误。其中Role已经存入到数据表,目的是想添加EditPrivilege以及中间表的数据。
既然报错了,那就先让我看看抛出这个Exception的源码吧。
public void onPersist(PersistEvent event, Map createCache) throws HibernateException {
// ....省略一些代码
// 下面我们看到entityState的值为DETACHED,就是从这里获得的
EntityState entityState = getEntityState( entity, entityName, entityEntry, source );
if ( entityState == EntityState.DETACHED ) {
EntityPersister persister = source.getFactory().getEntityPersister( entityName );
// 因为在区别PERSISTENT与DETACHED的时候通过ForeignKeys.isTransient来判断,
// 为了防止用户自定义的id生成策略使得框架发送误判,这里对自定义id策略的情况再次获取entityState
if ( ForeignGenerator.class.isInstance( persister.getIdentifierGenerator() ) ) {
persister.setIdentifier( entity, null, source );
entityState = getEntityState( entity, entityName, entityEntry, source );
}
}
switch ( entityState ) {
case DETACHED: { // 错误来源于这里,说明现在需要保存对象的entityState为Detached
throw new PersistentObjectException(
"detached entity passed to persist: " +
getLoggableName( event.getEntityName(), entity )
);
}
// 省略....
}
}
从上面的源码中看到,当前实体的entityState被判断为DETACHED,下面我们看getEntityState
方法:
/**
* Determine whether the entity is persistent, detached, or transient
*
* @param entity The entity to check
* @param entityName The name of the entity
* @param entry The entity's entry in the persistence context
* @param source The originating session.
*
* @return The state.
*/
protected EntityState getEntityState(
Object entity,
String entityName,
EntityEntry entry, //pass this as an argument only to avoid double looking
SessionImplementor source) {
//省略...
// 就是下面这行判断出当前实体的entityState为DETACHED,getAssumedUnsaved()返回null
if ( ForeignKeys.isTransient( entityName, entity, getAssumedUnsaved(), source ) ) {
return EntityState.TRANSIENT;
}
return EntityState.DETACHED;
}
让我们在往下面看ForeignKeys.isTransient方法
/**
* Is this instance, which we know is not persistent, actually transient?
*
* If assumed is non-null, don't hit the database to make the determination, instead assume that
* value; the client code must be prepared to "recover" in the case that this assumed result is incorrect.
*
* @param entityName The name of the entity
* @param entity The entity instance
* @param assumed The assumed return value, if avoiding database hit is desired
* @param session The session
*
* @return {@code true} if the given entity is transient (unsaved)
*/
public static boolean isTransient(String entityName, Object entity, Boolean assumed, SessionImplementor session) {
// 如果当前实体是LAZY的,返回false
if ( entity == LazyPropertyInitializer.UNFETCHED_PROPERTY ) {
return false;
}
// 让拦截器去检查
Boolean isUnsaved = session.getInterceptor().isTransient( entity );
if ( isUnsaved != null ) {
return isUnsaved;
}
// 让persister来检查,经过调试,就是这里返回false,让我们看persister.isTransient是如何判断的
final EntityPersister persister = session.getEntityPersister( entityName, entity );
isUnsaved = persister.isTransient( entity, session );
if ( isUnsaved != null ) {
return isUnsaved;
}
// 因为上面getAssumedUnsaved()返回的值为null,所以这里不返回
if ( assumed != null ) {
return assumed;
}
//上述方法都不行,则访问数据库来判断当前实体的状态
final Object[] snapshot = session.getPersistenceContext().getDatabaseSnapshot(
persister.getIdentifier( entity, session ),
persister
);
return snapshot == null;
}
persister.isTransient方法
public Boolean isTransient(Object entity, SessionImplementor session) throws HibernateException {
final Serializable id;
if ( canExtractIdOutOfEntity() ) {
id = getIdentifier( entity, session );
}
else {
id = null;
}
// 当一个实体的id为null,就认为它是Transient
if ( id == null ) {
return Boolean.TRUE;
}
// 判断这个实例是否有时间戳或者版本号控制
if ( isVersioned() ) {
// 如果是,则判断实体的version有没有保存过,如果保存过,那返回false
Boolean result = entityMetamodel.getVersionProperty()
.getUnsavedValue().isUnsaved( version );
if ( result != null ) {
return result;
}
}
// 判断当前实体的id是否保存过,保存过则表示当前实体为Detached,返回false。
// 就是在这里,Role这个类的entityMetamodel.getIdentifierProperty().getUnsavedValue()为0,但是实体的id值不为0。
Boolean result = entityMetamodel.getIdentifierProperty()
.getUnsavedValue().isUnsaved( id );
if ( result != null ) {
return result;
}
// check to see if it is in the second-level cache
if ( session.getCacheMode().isGetEnabled() && hasCache() ) {
final EntityRegionAccessStrategy cache = getCacheAccessStrategy();
final Object ck = cache.generateCacheKey( id, this, session.getFactory(), session.getTenantIdentifier() );
final Object ce = CacheHelper.fromSharedCache( session, ck, getCacheAccessStrategy() );
if ( ce != null ) {
return Boolean.FALSE;
}
}
return null;
}
后来发现问题是因为设置了Cascade.All,当执行save的时候,会调用onPersist()方法,这个方法会递归调用外联类(即Role)的onPersist()进行级联新增,但是roles已经添加了,因此报detached entity passed to persist。后来我将级联操作取消就ok了(其实只要将cascadeType.persist去掉就可以)。
所以当我们设计模型之间的级联关系的时候,要考虑好应该采用何种级联规则。