最近需要增加业务,需要增加一对一关联,由于系统采用hibernate3,而且使用了xml配置方式,所以就举例一下xml配置方式:
假设我们有两个类,人和身份证。我们要达到的效果就是查询人的信息时自动读取身份证。
类:
//人
public class Person(){
Long id;
IdCard idCard;
}
// 身份证
public class IdCard(){
Long id;
String cardNo;
}
接下来是xml的配置:
这样配置后,如果数据一致是没有问题的,但是由于我们是后加业务,所以肯定会造成数据不一致的情况,意思就是有人的信息,但是没有身份证的信息。这样一来,hibernate会报错:
No row with the given identifier exists
让我们阅读源码,了解出错原因(之前已通过查阅异常栈定位到出错位置):
// EntityType 有几个子类,ManyToOneType,OneToOneType
public class EntityType{
protected final Object resolveIdentifier(Serializable id, SessionImplementor session) throws HibernateException {
...
boolean isProxyUnwrapEnabled = unwrapProxy &&
session.getFactory()
.getEntityPersister( getAssociatedEntityName() )
.isInstrumented( session.getEntityMode() );
// 调用查询,传了传了一个isNullable()用于处理为空的情况
// isNullable()是抽象的,又子类实现
Object proxyOrEntity = session.internalLoad(
getAssociatedEntityName(),
id,
eager,
isNullable() && !isProxyUnwrapEnabled
);
...
}
}
// 由于我们使用OneToOne,所以我们看一下OneToOneType的isNullable实现:
public class OneToOneType extends EntityType {
...
protected boolean isNullable() {
// 可以看到是根据foreignKeyType来进行判断,稍后我们来看一下foreignKeyType是如何来的
return foreignKeyType==ForeignKeyDirection.FOREIGN_KEY_TO_PARENT;
}
...
}
// 调用SessionImpl进行查询
public class SessionImpl{
public Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException {
// todo : remove
LoadEventListener.LoadType type = nullable
? LoadEventListener.INTERNAL_LOAD_NULLABLE
: eager
? LoadEventListener.INTERNAL_LOAD_EAGER
: LoadEventListener.INTERNAL_LOAD_LAZY;
LoadEvent event = new LoadEvent(id, entityName, true, this);
// 调用查询
fireLoad(event, type);
if ( !nullable ) {
UnresolvableObjectException.throwIfNull( event.getResult(), id, entityName );
}
return event.getResult();
}
}
// fireLoad调用了DefaultLoadEventListener的load
public class DefaultLoadEventListener {
...
protected Object load(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadEventListener.LoadType options) {
...
// 读取关联对象
Object entity = doLoad(event, persister, keyToLoad, options);
boolean isOptionalInstance = event.getInstanceToLoad() != null;
// 进行为空处理,如果为空直接抛异常
if ( !options.isAllowNulls() || isOptionalInstance ) {
if ( entity == null ) {
event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound( event.getEntityClassName(), event.getEntityId() );
}
}
...
return entity;
}
...
}
在上面的代码里我们可以看到,在关联对象为空时,hibernate通过调用"options.isAllowNulls()和isOptionalInstance"来判断是否需要抛异常。接下来我们再研究一下options.isAllowNulls()是如何来的。
接上面的话,我们看一下OneToOneType的foreignKeyType是如何来的,观察一下构建hbm时做了什么:
public final class HbmBinder {
// 绑定one-to-one
public static void bindOneToOne(Element node, OneToOne oneToOne, String path, boolean isNullable,
Mappings mappings) throws MappingException {
bindColumns( node, oneToOne, isNullable, false, null, mappings );
Attribute constrNode = node.attribute( "constrained" );
boolean constrained = constrNode != null && constrNode.getValue().equals( "true" );
oneToOne.setConstrained( constrained );
// 如果constrained为true则ForeignKeyType就为FOREIGN_KEY_FROM_PARENT
oneToOne.setForeignKeyType( constrained ?
ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT :
ForeignKeyDirection.FOREIGN_KEY_TO_PARENT );
...
}
}
所以最终的解决办法:去掉constrained="true":
那么constrained的意思是什么呢,通过阅读以上代码,我们得出结论,当constrained为true时,表明关联表肯定存在对应的键与主表进行对应。
至于其他意思请百度。