Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent Hibernate lazy loading #97

Closed
marcelolucio1982 opened this issue Apr 2, 2014 · 23 comments
Closed

Prevent Hibernate lazy loading #97

marcelolucio1982 opened this issue Apr 2, 2014 · 23 comments
Labels

Comments

@marcelolucio1982
Copy link

Hi,

Is there any way to avoid Lazy Loading launched by hibernate? With the dozer can create a CustomFieldMapper that manipulates these values​​, but found the ModelMapper much simpler

public class MyCustomFieldMapper implements CustomFieldMapper 
{
    public boolean mapField(Object source, Object destination, Object sourceFieldValue, ClassMap classMap, FieldMap fieldMapping) {       

        if (!(sourceFieldValue instanceof PersistentSet)) {
            return false;
        }


        if (((PersistentSet) sourceFieldValue).wasInitialized()) {

            return false;
        }
        destination = null;
        return true;
    }   
}
@jhalterman
Copy link
Member

I haven't thought about this problem specifically, but there are probably a few ways you can approach it. One is to set a global Condition that basically only maps properties that are not PersistentSets (or whatever.

More on conditions.

@marcelolucio1982
Copy link
Author

Try with Condition, but I need the property 'parentSource'. It is with this property that I check if the object type is Javassist or persistentBag.
This property is not public

Object parentSource() {
    return parentSource;
  }

@jhalterman
Copy link
Member

You can access the parent context and it's source via context.getParent().getSource()

@marcelolucio1982
Copy link
Author

The method 'applies' the Condition class is call after the 'resolveSourceValue' method, and this method is the exception thrown on line 189

source = accessor.getValue(source);

Entity root:

@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    /.../   
    @ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST})
    private Endereco endereco;

Entity Endereco

@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String bairro;

    private String complemento;

DTO

private Long id;

    private Long enderecoId;

    private String enderecoBairro;

    private String enderecoComplemento;

@marcelolucio1982
Copy link
Author

No consideration for this issue?

@jhalterman
Copy link
Member

You are right about the Condition - it would need to be set for an entire TypeMap, not the property. Right now you can accomplish this via TypeMap.setCondition. So:

modelMapper.createTypeMap(Source.class, Dest.class).setCondition(notLazyCondition);

Unfortunately right now you'd need to set this on each TypeMap you use. One solution is I could add support for a global Condition which you set against the configuration. Would this be good?

@marcelolucio1982
Copy link
Author

Would be very good. The Jackson project has a module responsible for disable the lazy loading. You could use the same idea.

@nocny-x
Copy link

nocny-x commented Nov 19, 2014

hi, did this problem fixed?

@jhalterman
Copy link
Member

The ability to configure a global property condition was added a while back. I just committed a test that demonstrates skipping lazily fetched JPA properties using a condition. Basic usage (for Hibernate):

    modelMapper.getConfiguration().setPropertyCondition(new Condition<Object, Object>() {
      public boolean applies(MappingContext<Object, Object> context) {
        return !(context.getSource() instanceof PersistentCollection);
      }
    });

@nocny-x
Copy link

nocny-x commented Nov 19, 2014

Thanks! it works.

@vmf
Copy link

vmf commented Apr 17, 2018

Hi there! I'm working on a project that aims to solve common JPA problems when mapping entities to DTOs using ModelMapper. This issue has already been solved on the project. Project link: JPA Model Mapper

Thomas-Adams added a commit to Thomas-Adams/internet that referenced this issue Dec 23, 2019
@marcusvoltolim
Copy link

Using lambda:

modelMapper.getConfiguration().setPropertyCondition(context -> !(context.getSource() instanceof PersistentCollection))

@malkochoglu
Copy link

malkochoglu commented Sep 23, 2020

Is there a way to do it selectively per modelmapper.map(..) execution? Not globally as discussed? I have a case that I leave the decision to API caller to request nested JPA objects. Let's say if an optional parameter is set to false nested object is null, if true API returns nested object as well.

@micobarac
Copy link

micobarac commented Apr 7, 2021

Well, I have the same problem. The proposed condition above didn't work for me.

Without it, I get this error:

ModelMapper mapping errors:\n\n1) Converter org.modelmapper.internal.converter.MergingCollectionConverter@6a8e1457 failed to convert org.hibernate.collection.internal.PersistentSet to java.util.Set.\nCaused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

With the proposed solution, I still get the error:

failed to lazily initialize a collection of role: ...

@chhsiao90
Copy link
Member

Hi @micobarac ,

Can you provide reproducible code or more information? Thanks.

@micobarac
Copy link

/**
 * An Account.
 */
@Entity
@Table(name = "account")
public class Account implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Size(max = 100)
    @Pattern(regexp = Constants.ACCOUNT_REGEX, message = Constants.ACCOUNT_MESSAGE)
    @Column(name = "name", nullable = false)
    private String name;

    @ManyToOne
    private Group group;
    ...
}

/**
 * A Group.
 */
@Entity
@Table(name = "jhi_group")
public class Group implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Size(min = 1, max = 255)
    @Pattern(regexp = Constants.NAME_REGEX, message = Constants.NAME_MESSAGE)
    @Column(name = "name")
    private String name;

    @JsonIgnore
    @ManyToMany(mappedBy = "groups")
    private Set<Account> accounts = new HashSet<>();

    @OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<GroupService> services = new HashSet<>();
    ...
}

When I try to map Account i get from the database to a new Account (even though apply the proposed mapper condition), I get:

failed to lazily initialize a collection of role: group.services

@chhsiao90
Copy link
Member

Have you tried the solution mentioned above?

@micobarac
Copy link

#97 (comment)

@chhsiao90
Copy link
Member

Do you have full stacktrace and example/reproducible code?

@micobarac
Copy link

No, unfortunatelly not. It's a huge project with big and interconnected entities. The point is that the solution above solves the problem of preventing mapper to map the persistent collections, but nevertheless, it triggers those lazy loaded getters somehow and throws the error of trying to initialize lazy loaded collection.

@chhsiao90
Copy link
Member

I guessed the solution provided above skip the property, so the lazy loaded getters won't be called if it was configured correctly.

I can't see how your code and how you configured modelmapper, I'm not sure how I can help to fix this problem. Maybe you can write a minimum reproducible code for this?

@elibus
Copy link

elibus commented May 16, 2021

I confirm the solution does not work for me either. It looks like the condition is called after the attempt to initialize the lazy loaded property.

@elibus
Copy link

elibus commented May 17, 2021

I debugged a bit and it looks to me the conditions are applied after resolveSourceValue call.

    Object source = resolveSourceValue(context, mapping);
    MappingContextImpl<Object, Object> propertyContext = propertyContextFor(context, source,
        mappingImpl);

    Condition<Object, Object> condition = (Condition<Object, Object>) Objects.firstNonNull(
        mapping.getCondition(),
        context.getTypeMap().getPropertyCondition(),
        configuration.getPropertyCondition());
    if (condition != null) {
      boolean conditionIsTrue = condition.applies(propertyContext);
      if (conditionIsTrue && mapping.isSkipped()) // when(condition).skip()
        return;
      else if (!conditionIsTrue && !mapping.isSkipped()) { // when(condition)
        context.shadePath(propertyPath);
        return;
      }
    }

resolveSourceValue calls this:

source = accessor.getValue(source);

which is triggering the error:

021-05-17T02:04:28,555+02  INFO 13242  [http-nio-8080-exec-2] e.i.e.RestResponseEntityExceptionHandler : Return 400: org.hibernate.LazyInitializationException: could not initialize proxy [com.test.User#2] - no Session
        at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:170) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
        at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:310) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
        at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
        at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.4.30.Final.jar:5.4.30.Final]
        at com.test.User$HibernateProxy$lawZxnDf.getCreateTimestamp(Unknown Source) ~[main/:?]
        ... suppressed 4 lines
        at org.modelmapper.internal.PropertyInfoImpl$MethodAccessor.getValue(PropertyInfoImpl.java:101) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.resolveSourceValue(MappingEngineImpl.java:197) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:170) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:151) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:105) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:71) ~[modelmapper-2.4.3.jar:?]
        ... 141 more
Wrapped by: java.lang.reflect.InvocationTargetException
        ... suppressed 4 lines
        at org.modelmapper.internal.PropertyInfoImpl$MethodAccessor.getValue(PropertyInfoImpl.java:101) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.resolveSourceValue(MappingEngineImpl.java:197) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:170) ~[modelmapper-2.4.3.jar:?]
        at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:151) ~[modelmapper-2.4.3.jar:?]

Am I missing anything?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants