问题与分析

某日忽然发现在用postman测试数据时报错如下:

com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy [com.cbxsoftware.cbx.attachment.entity.RefAttachment#c109ec36e60c4a89a10eabc72416d984] - no Session (through reference chain: com.cbxsoftware.cbx.sampletracker.elasticsearch.entity.SampleTrackerDetailEstc["sampleTracker"]->com.cbxsoftware.cbx.sampletracker.elasticsearch.entity.SampleTrackerEstc["sampleTracker"]->com.cbxsoftware.cbx.sampletracker.entity.SampleTracker["item"]->com.cbxsoftware.cbx.item.entity.RefItem["image"]->com.cbxsoftware.cbx.image.entity.RefImage["propFormat"]->com.cbxsoftware.cbx.attachment.entity.RefAttachment$HibernateProxy$uNA5RwMT["revision"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:394)
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:353)
at com.fasterxml.jackson.databind.ser.std.StdSerializer.wrapAndThrow(StdSerializer.java:316)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:727)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:120)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3219)
...
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [com.cbxsoftware.cbx.attachment.entity.RefAttachment#c109ec36e60c4a89a10eabc72416d984] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at com.cbxsoftware.cbx.attachment.entity.RefAttachment$HibernateProxy$uNA5RwMT.getRevision(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
... 128 common frames omitted

报错很明显,是由于hibernate的懒加载引起的。项目使用的是SpringBoot框架,JPA默认使用的是hibernate的实现,而hibernate的懒加载机制其实就是延迟加载对象,如果没有在session关闭前使用到对象里除id以外的属性时,就只会返回一个没有初始化过的包含了id的代理类。很多时候,这个代理类会引发上述的异常。

简单说一下为什么会触发懒加载异常,首先hibernate开启一个session(会话),然后开启transaction(事务),接着发出sql找回数据并组装成pojo(或者说entity、model),这时候如果pojo里有懒加载的对象,并不会去发出sql查询db,而是直接返回一个懒加载的代理对象,这个对象里只有id。如果接下来没有其他的操作去访问这个代理对象除了id以外的属性,就不会去初始化这个代理对象,也就不会去发出sql查找db。接着事务提交,session关闭。如果这时候再去访问代理对象除了id以外的属性时,就会报上述的懒加载异常,原因是这时候已经没有session了,无法初始化懒加载的代理对象。

解决方法一

如果是spring继承的hibernate,根据上述的原因,可以延长session的生命周期,但是这里用的是SpringBoot的JPA,处理方法不同,需要在application.properties配置下懒加载相关的东西:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

进行该配置后,可以在session关闭时也能另外开启一个新的session和事务来访问db以取回懒加载对象的数据。

解决方法二

因为该懒加载异常是缺少session导致的,那么可以通过在方法前添加事务注解@Transactional的方式来解决,只要事务没有提交,session就不会关闭,自然就不会出现上述的懒加载异常。不过由于该事务注解是用Spring AOP实现的,存在着一些坑,比如类内直接调用无效或者对非public方法无效等,需要多加注意。

当使用了上述两种方法后,发现不再触发LazyInitializationException,但是却发生了另一个新的异常InvalidDefinitionException

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.cbxsoftware.cbx.item.elasticsearch.entity.ItemEstc["mainEntity"]->com.cbxsoftware.cbx.item.entity.Item["image"]->com.cbxsoftware.cbx.image.entity.RefImage["propFormat"]->com.cbxsoftware.cbx.attachment.entity.RefAttachment$HibernateProxy$vTKSYzrN["hibernateLazyInitializer"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:313)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:71)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:33)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanSerializer.serialize(UnwrappingBeanSerializer.java:120)
at com.fasterxml.jackson.databind.ser.impl.UnwrappingBeanPropertyWriter.serializeAsField(UnwrappingBeanPropertyWriter.java:127)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:480)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:319)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:3905)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:3219)
...

这个异常是由于hibernate在代理类里添加了一个属性hibernateLazyInitializer,当该对象转换成json的时候就会报错。解决方法是将该属性过滤掉,可以在对应的类名或者公共类前加上如下注解:

@JsonIgnoreProperties(value = { "hibernateLazyInitializer" })

源码分析

因为对懒加载异常的发生有些好奇,所以看了下hibernate的源码,这里简单分析下,另外我看的是两个源码包如下:

spring-orm-5.1.5.RELEASE.jar
hibernate-core-5.3.7.Final.jar

首先是关于spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true的配置,前面半截是因为JPA集成了hibernate的配置,所以在hibernate中,这个配置应该是hibernate.enable_lazy_load_no_trans=true

在hibernate的一个常量接口org.hibernate.cfg.AvailableSettings中定义了各种配置常量,其中就包括上述这个配置:

String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans";

在启动项目的时候会读取配置文件,将其解析为一个HashMap<K,V>,这些参数在newEntityManagerFactoryBuilderImpl的时候被使用到,上面的常量会在org.hibernate.boot.internal.SessionFactoryOptionsBuilder里被拿来初始化:

this.initializeLazyStateOutsideTransactions = cfgService.getSetting( ENABLE_LAZY_LOAD_NO_TRANS, BOOLEAN, false );

因为在配置文件里配置了该变量的值为true,所以这里在初始化的时候就会把initializeLazyStateOutsideTransactions的值设置为true。该变量由一个方法来判断其值是否为true:

	@Override
public boolean isInitializeLazyStateOutsideTransactionsEnabled() {
return initializeLazyStateOutsideTransactions;
}

接着在组装pojo时,会为懒加载对象创建对应的代理对象,当需要获取该代理对象除id以外的属性时,就会调用AbstractLazyInitializer#initialize()进行初始化,逻辑如下:

	@Override
public final void initialize() throws HibernateException {
if ( !initialized ) {
if ( allowLoadOutsideTransaction ) {
permissiveInitialization();
}
else if ( session == null ) {
throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - no Session" );
}
else if ( !session.isOpen() ) {
throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session was closed" );
}
else if ( !session.isConnected() ) {
throw new LazyInitializationException( "could not initialize proxy [" + entityName + "#" + id + "] - the owning Session is disconnected" );
}
else {
target = session.immediateLoad( entityName, id );
initialized = true;
checkTargetState(session);
}
}
else {
checkTargetState(session);
}
}

如果在配置文件中设置了spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true,那么上述的allowLoadOutsideTransaction变量值就为true,则可以进入permissiveInitialization()方法另起session和事务,最终避免懒加载异常LazyInitializationException。如果没有配置该参数,那么就会由于session已关闭(即为null)而抛出LazyInitializationException

参考链接

SpringBoot JPA懒加载异常 - com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy的更多相关文章

  1. 400 bad Request: JackSon将json串转成List<Object>,异常com.fasterxml.jackson.databind.JsonMappingException

    最近遇到的400, 以前总以为 400 就是参数不匹配造成的,直到今天遇到这个问题 控制台报错如下:  com.fasterxml.jackson.databind.JsonMappingExcept ...

  2. jpa懒加载异常

    1.项目背景概述 事情是这样子的,使用了spring data jpa的项目jeesite jeesite的实体中使用了懒加载模式. 并且一个实体类中还不止一个属性设置了懒加载模式. 项目本身已经存在 ...

  3. Spring Boot JPA 懒加载

    最近在使用spring jpa 的过程中经常遇到懒加载的错误:"` org.hibernate.LazyInitializationException: could not initiali ...

  4. 关于Web项目出现懒加载异常的解决方案

    manytomany关系中,使用 fetch = FetchType.LAZY 来做懒加载,加快些性能.但是却一直出错,原因是session被关闭,要保持session,需要事务. Hibernate ...

  5. dubbo序列化hibernate.LazyInitializationException could not initialize proxy - no Session懒加载异常的解决

    dubbo序列化,hibernate.LazyInitializationException could not initialize proxy - no Session懒加载异常的解决 转载声明: ...

  6. 在web.xml中添加配置解决hibernate 懒加载异常

    在web.xml添加如下,注意:在配置在struts2的拦截器之前,只能解决请求时出现的懒加载异常:如果没有请求,还需要lazy属性的添加(比如过滤器) <!-- 配置Spring的用于解决懒加 ...

  7. Hibernate和Spring整合出现懒加载异常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    出现问题:  SSH整合项目里,项目目录结构如下: 在EmployeeAction.java的list()方法里将employees的list放入到request的Map中. EmployeeActi ...

  8. 【jackson 异常】com.fasterxml.jackson.databind.JsonMappingException异常处理

    项目中,父层是Gene.java[基因实体]  子层是Corlib.java[文集库实体],一种基因对用多个文集库文章 但是在查询文集库这个实体的时候报错:[com.fasterxml.jackson ...

  9. com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input

    作者原创,转载请注明转载地址 第一次遇到该异常,在网上搜了很长时间也没找到解决答案,特此记录 1.异常展示: com.fasterxml.jackson.databind.JsonMappingExc ...

随机推荐

  1. .net core使用CSRedisCore连接哨兵集群,并用作redis使用分布式缓存。

    CSRedisCore是国内大佬出品的一个Redis-Cli-SDK. Github地址:https://github.com/2881099/csredis 使用此插件作为分布式缓存也十分简单. 一 ...

  2. django.http.request中QueryDict 对象

    在一个 HttpRequest 对象中, GET 和 POST 属性都是 django.http.QueryDict 的实例. QueryDict 是一个类似于字典的类,专门用来处理用一个键的多值.当 ...

  3. ios下按钮click事件点击穿透问题

    和app进行混合开发的时候,一个页面使用h5写的,按钮上绑定click事件会触发下面图片上的a链接导致跳转,页面如图 顶部是一个banner,分vr.视频.图片三部分,红框处的三个nav按钮绑定cli ...

  4. SAP-参数(条件表)配置教程–GS01/GS02/GS03

    转载:http://www.baidusap.com/abap/others/2849 在SAP开发中,某段代码运行可能需要满足某个条件,通常解决办法有两种:一种是在代码中写死限制条件,此种方式当限制 ...

  5. MySQL binlog反解析

    反解析delete语句 背景:delete table忘了加条件导致整张表被删除 恢复方式:直接从binlog里反解析delete语句为insert进行恢复 导出删指定表的DELETE语句: # my ...

  6. JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止

    JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止 背景 当单线程的程序发生一个未捕获的异常时我们可以采用try....catch进行异常的捕获,但是在多线程环境 ...

  7. 【robotframework】robotframework环境搭建

    一.基于python3.6环境 在dos命令输入 pip install robotframework 在线安装robotframework在dos命令输入 pip install Pypubsub= ...

  8. Linux 之 搜索

    locate - 文件名搜索命令 用于查找文件 格式为:locate 文件名 该命令用于查找符合条件的文件,它会去保存文件与目录名称的数据库内,查找合乎范本样式条件的文件或目录. 因为该命令是直接在数 ...

  9. Django组件之forms

    forms:校验字段功能 针对一个实例:注册用户讲解. 模型:models.py class UserInfo(models.Model): name=models.CharField(max_len ...

  10. zstu月赛 招生

    题目 浙江理工大学招生,一开始有0名学生报考,现在有如下几种情况: 1.增加一名报考学生,报考学生成绩为x: 2.一名成绩为x的学生放弃报考. 3.从现在报考的学生来看,老师想知道如果要招生至少x名学 ...