SpringBoot JPA lazy loading abnormal - com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy

Issues and Analysis

One day suddenly found that when a postman test data given as follows:

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

Clearly an error, due to hibernate lazy loading caused. Project using SpringBoot framework, JPA default is to achieve hibernate, and hibernate lazy loading mechanism is actually a delay loading the object, if not used to the object in the property other than id before the session closed, it will only return a not initialized contains the id of the proxy class. In many cases, this will lead to the above proxy class exception.

Briefly about why would trigger an exception lazy loading, first of all hibernate open a session (session), then turn transaction (a transaction), then sent to retrieve sql data and assembled into pojo (or entity, model), this time if there pojo lazy loaded objects, and not to issue sql query db, but directly return a proxy object lazy loading, this object only id. The next operation if no other access to the proxy, except for the id attribute, they will not initialize the proxy object, there is not going to send out to find sql db. Then the transaction commits, session closed. If you go this time in addition to access to the proxy object attributes id, it will report the above abnormal lazy loading, because this time has no session, and could not be initialized proxy object lazy loaded.

Solution one

If spring is inherited hibernate, according to the above reasons, the session can extend the life cycle, but here is a different JPA, treatment SpringBoot, the need application.propertieslazy loading things related Configuration:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

After this configuration, it can further open a new session and the session is closed when the transaction to access data db lazy loading to retrieve the object.

Solution two

Because of the lazy loading exceptions are due to the lack of session, you can add notes by the transaction before the method @Transactionalway to solve, as long as the transaction is not submitted, it will not close the session, naturally, does not appear abnormal above lazy loading. However, because the transaction annotation with Spring AOP is implemented, there are some pits, such as call invalid or void for non-public methods, need to pay more attention directly within the class.

When using either of these methods, they found no trigger LazyInitializationException, but it happened another new exception 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)
    ...

This anomaly is due to hibernate in the proxy class to add an attribute hibernateLazyInitializer, when the object is converted into json time will be given. The solution is filtered off properties can be added as annotation or class name corresponding to the front public class:

@JsonIgnoreProperties(value = { "hibernateLazyInitializer" })

Source code analysis

Because of lazy loading exception occurs curious, so I looked under hibernate source code, under the simple analysis here, and I see two source package is as follows:

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

The first is about the spring.jpa.properties.hibernate.enable_lazy_load_no_trans=trueconfiguration, the front half is due to the integration of the JPA hibernate configuration, so in hibernate, this configuration should be hibernate.enable_lazy_load_no_trans=true.

In a constant interface to the hibernate org.hibernate.cfg.AvailableSettingsdefined constants in various configurations, including the configuration described above:

String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans";

When starting a project reads the configuration file, parses it into a HashMap<K,V>, these parameters in the new EntityManagerFactoryBuilderImplto be used to the time constant of the above will org.hibernate.boot.internal.SessionFactoryOptionsBuilderbe used to initialize in:

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

Because in the configuration file to configure the variable is true, so there will be initialized when the initializeLazyStateOutsideTransactionssetting value to true. This variable is set by a method to determine if its value is true:

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

Next POJO when assembled, will be created for lazy loading proxy object corresponding to the object, when it is desired to obtain the proxy object other than the id attribute, is called AbstractLazyInitializer#initialize()to initialize, logic is as follows:

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

If you set in the configuration file spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true, then the above-mentioned allowLoadOutsideTransactionvariable value is true, then you can enter permissiveInitialization()another method from session and transaction ultimately avoid abnormal lazy loading LazyInitializationException. If this parameter is missing, it will be because the session is closed (that is, null) and throws LazyInitializationException.

Reference links

Guess you like

Origin www.cnblogs.com/yulinlewis/p/11730203.html