JPA javax.persistence.EntityNotFoundException: Unable to find XXXX with id 0

奇怪bug,第一次查询有值,同样表单进行 第二次查询就报错。
找到解决办法:

原文:

问题原因:

无论是@OneToOne 还是@ManyToOne,出现这个原因都是因为子表(被关联表)中没有主表(关联表)中ID所对应的记录。

解决办法:(直接在OneToOne或者ManyToOne那个字段加 @NotFound(action=NotFoundAction.IGNORE)这个注解)

1. 检查为什么子表中没有主表中ID对应的记录

2. 如果子表中没有主表ID对应的记录也可以正常加载数据,那么需要在主表字段上加一个@NotFound Annotation。示例如下:

@OneToOne(optional=false)
@JoinColumn(name="business_id")
@NotFound(action=NotFoundAction.IGNORE)
private Business business;

这样,当子表中没找到数据时,主表中对应的field就是null,而不会报错了。
深究:

原文标题(从Hibernate源码看@NotFound(action=NotFoundAction.IGNORE) 会导致Eager加载 @ManyToOne(fetch = FetchType.LAZY)无效

记得刚进项目组的时候leader给我介绍过NotFound(action=NotFoundAction.IGNORE)会导致Fetch无效,这样的话无论你设置fetchType是lazy还是eager都会以eager加载,一开始我开始模糊的记住,但是过不了2天就会忘,今天有个任务也是配Ignore的,索性就看看里面到底是怎么整的,下面把发现的贴出来:

先看org.hibernate.type.ManyToOneType

public class ManyToOneType extends EntityType {
    
    private final boolean ignoreNotFound;
 
    public ManyToOneType(String className) {
        this( className, false );
    }
 
    public ManyToOneType(String className, boolean lazy) {
        super( className, null, !lazy, true, false );
        this.ignoreNotFound = false;
    }

这里可以清楚的看到对ManyToOneType来说 默认的是eager加载(前两个红色标注),ignoreNotFound是false,也就是说默认的NotFound不是ignore,即是Exception。

好的,接着往下看org.hibernate.impl.SessionImpl的internalLoad方法:

 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();
    }

这里假设你ManyToOne上设置的是NotFoundAction.IGNORE,ignoreNotFound这个属性就会设置为true,掉这个方法时传进来的boolean nullable就是true,这是大前提。

看红色字体的三目运算符,a?b:c,计算规则,先计算a,a为true的话计算b的值返回,a为false计算c的值返回。对a?b:c?d:e这个有点复杂,可以这样换算a?b(c?d:e).也就是说a为false的话计算c?d:e的值返回。

由于nullable是true,咱们就看INTERNAL_LOAD_NULLABLE 这几个常量的值。

下面看org.hibernate.event.LoadEventListener:

public static final LoadType INTERNAL_LOAD_EAGER = new LoadType("INTERNAL_LOAD_EAGER")
            .setAllowNulls(false)
            .setAllowProxyCreation(false)
            .setCheckDeleted(false)
            .setNakedEntityReturned(false);
    
    public static final LoadType INTERNAL_LOAD_LAZY = new LoadType("INTERNAL_LOAD_LAZY")
            .setAllowNulls(false)
            .setAllowProxyCreation(true)
            .setCheckDeleted(false)
            .setNakedEntityReturned(false);
    
    public static final LoadType INTERNAL_LOAD_NULLABLE = new LoadType("INTERNAL_LOAD_NULLABLE")
            .setAllowNulls(true)
            .setAllowProxyCreation(false)
            .setCheckDeleted(false)
            .setNakedEntityReturned(false);

这里看到 .setAllowProxyCreation(false) 也就是代理创建false,这样话就不是懒加载!!!看到设置为lazy的是.setAllowProxyCreation(true),设置为eager的是.setAllowProxyCreation(false),基本能说明eager不创建代理,lazy是代理加载

最后再贴段代码证明哪里调用这布尔值:

org.hibernate.event.def.DefaultLoadEventListener

的proxyOrLoad方法:

if ( options.isAllowProxyCreation() ) {
                    return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext );
                }
                else {
                    // return a newly loaded object
                    return load(event, persister, keyToLoad, options);
                }
            }

看hibernate上的注解写的很清楚,如果AllowProxyCreation这布尔值为false的话会返回一个新的加载对象!

从上面的代码不难从hibernate框架的代码中看出hibernate的一些默认设置方式。这里还多亏maven的应用,要是以前不用maven,想一窥ssh的源码还要自己下载,自己加载进去,效率低而繁琐,万一换个workspase就得重新来一遍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值