0

I'm struggling with editing and updating nested objects because of this Hibernate Lazy Loading error that I keep hitting again and again.

In my application:

  1. I bind an Image Set to a product.
  2. I've used the formatter to convert the imageset for use in a select box, so the user can select the image set.
  3. It works well if the form succeeds, the object binds and the relationship saves correctly.
  4. But if there's a validation failure, whenever the UI layer tries to access the nested object value I get the dreaded Lazy Initialization/No Session error!

So I tried to write a method to initialize the related objects as a workaround:

@Transactional
public void initObject(Variant variant) {
    Hibernate.initialize(variant.getOption1());
    Hibernate.initialize(variant.getOption2());
    Hibernate.initialize(variant.getOption3());
    Hibernate.initialize(variant.getImageSet());
}

The first 3 lines in that method work (these are unidirectional one to one), but the fourth doesn't (this is a many to one), it still says no proxy.

I've also tried making everything Eager fetch type.

Neither of these worked.

The issue is probably that I'm creating the parent object, it doesn't get saved to the database when there's a validation fault, but the nested object is for whatever reason a lazy proxy object regardless of the fetch type (see formatter below).

How can I fix this?

This is the formatter component:

@Component
public class ImageSetFormatter implements Formatter<ProductImageSet> {

    private static Logger logger = LogManager.getLogger(VariantController.class.getName());

    @Autowired
    private ProductImageService imageService;

    @Override
    public ProductImageSet parse(String s, Locale locale) throws ParseException {
        logger.entry();

        Long imageSetId = Long.valueOf(s);
        logger.exit();
        return imageService.getImageSet(imageSetId);
    }

    @Override
    public String print(ProductImageSet productImageSet, Locale locale) {
        logger.entry();

        logger.exit();
        return Long.toString(productImageSet.getId());
    }
}

Stacktrace when the line Hibernate.initialize(variant.getImageSet()) is called:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:973) org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863) javax.servlet.http.HttpServlet.service(HttpServlet.java:644) org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837) javax.servlet.http.HttpServlet.service(HttpServlet.java:725) org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103) org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)

Relevant Object Relationships:

   @Entity
@Table(name="variants")
@EntityListeners({AuditingEntityListener.class})
public class Variant extends AbstractAuditable<Customer, Long> {

    @OneToOne
    private VariantOptionValue option1;

    @OneToOne
    private VariantOptionValue option2;

    @OneToOne
    private VariantOptionValue option3;

    ...

     @ManyToOne
    @JoinColumn(name="image_set_id")
    @LazyCollection(LazyCollectionOption.FALSE )
    private ProductImageSet imageSet;



}


@Entity
@Table(name="product_image_set")
@EntityListeners({AuditingEntityListener.class})
public class ProductImageSet extends AbstractAuditable<Customer, Long> {


    public ProductImageSet(String label)
    {
        this.label = label;
    }

    public ProductImageSet(){

    }

    @Basic
    @Column(length = 50, nullable = false)
    private String label;

    @OneToMany(mappedBy = "imageSet", fetch = FetchType.EAGER)
    private List<ProductImage> images;

    @OneToMany(mappedBy = "imageSet", fetch = FetchType.EAGER)
    private List<Variant> variants;

    private int sequence;

    @ManyToOne
    @JoinColumn(name = "product_id")
    private Product product;

    ...
}
Jens Schauder
  • 65,795
  • 24
  • 148
  • 294
Richard G
  • 4,027
  • 10
  • 38
  • 79
  • Can you post the actual stacktrace of the exception that you are getting? The error message may give a clue as to what the error is. Can you also post your objects that you are using, or give a detailed description of their relationships (one X has many Y, one Y has one Z etc) – JamesENL Sep 16 '14 at 02:56
  • Can you post a description of object relationships as well? You talk about a parent object, but I need to know what is the parent and what is the child – JamesENL Sep 16 '14 at 03:03
  • Note that I tried a few variations already of fetch = FetchType.EAGER, the LazyCollection was just my latest attempt at a fix. – Richard G Sep 16 '14 at 06:16

1 Answers1

0

Two things you might want to try.

1- add the OpenEntityManagerInViewFilter

<filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    <init-param>
        <param-name>entityManagerFactoryBeanName</param-name>
        <param-value>entityManagerFactory</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

2- Add your entity/form/model to the session

@Controller
@SessionAttributes("yourEntityFormModel")
public class YourController {
    ...
}

--------- Update

What happen is that, in a method annotated that runs inside a transaction, Hibernate will be able to retrieve the relationships from that entity on demand.

So, the example below works fine.

@Transactional
public void updateEmployeesFromDepartment(int depId) {

    Department dep = departmentRepository.findOne(dep.getId());
    for (Employee e : dep.getEmployees()) {
        // update whatever you want
    }

}

But I don't expect this to work...

public void updateEmployeesFromDepartment(int depId) {

    Department dep = departmentRepository.findOne(dep.getId());
    for (Employee e : dep.getEmployees()) {
        // update whatever you want
    }

}
Desorder
  • 1,489
  • 12
  • 16
  • See [this](http://stackoverflow.com/questions/1538222/why-not-to-use-springs-openentitymanagerinviewfilter) for why number 1 is a bad idea. – JamesENL Sep 16 '14 at 03:01
  • Also, if this is a RESTful application, number 2 totally destroys any RESTful implementation as you are making making it stateful by adding the model to the session. – JamesENL Sep 16 '14 at 03:05
  • @JamesMassey I understand the point... arguable tho... This is one of those partners that once saved the day but people (!?!) don't like it anymore. I do prefer to load all required information to view beforehand but if I can help the friend here, he can decide later if he likes it or not. :) – Desorder Sep 16 '14 at 03:06
  • That's why I didn't downvote your answer. I agree that this was once a solution, but it might not be helpful in this case. I was just pointing out the possible flaws in case the OP didn't understand the implications – JamesENL Sep 16 '14 at 03:07
  • How did you find out it's a RESTful implementation? I couldn't find anything that specified that. – Desorder Sep 16 '14 at 03:09
  • I didn't, I said IF this is a RESTful application. – JamesENL Sep 16 '14 at 03:10
  • Sorry, I missed the "if". :) I'm assuming he is using a plain spring MVC controllers and views. In the case of a restful application, he would need to parse the Object graph to a different format (XML/JSON ?!?). The parsing would happen in the Spring side and it might work just as fine if his transaction demarcations are right. – Desorder Sep 16 '14 at 03:16
  • Agreed, that's what I believe the problem is. The OP's transaction demarcations are incorrect. – JamesENL Sep 16 '14 at 03:16
  • I've been avoiding the TransactionInView filter so far successfully. I do think it's a problem with the transaction demarcation. At the moment I'm declaring each Entity with it's own jpa repository implementation which I thought was right - but it seems that if I access another entity, it does something to the current transaction, hence why I tried to wrap the helper initializer with @Transactional. – Richard G Sep 16 '14 at 06:20