Spring MVC + Hibernate: unable to initialize proxy - no session

Note: See my own answer to this question for an example of how I solved this problem.

I am getting the following exception in my Spring MVC 4 + Hibernate 4 project:

org.hibernate.LazyInitializationException: Failed to lazily initialize the role collection: com.mysite.Company.Updates, Failed to initialize proxy - no session

After reading many other questions on this issue, I understand why this exception is happening, but I'm not sure how to fix it. I am doing the following:

  • My Spring MVC Controller calls a method on a service
  • Service method calls method in DAO class
  • DAO class method retrieves the object object via Hibernate and returns it to the calling service
  • The service returns the selected object to the controller
  • Controller passes object to view (JSP)
  • The view tries to iterate over a many-to-many association that is lazy loaded (and therefore a proxy).
  • The exception is thrown because the session is closed at this point and the proxy is unable to load associated data

I've worked with PHP and doctrine2 previously and this way of doing things didn't cause any problems. I'm trying to find the best way to solve this problem, because the solutions I've found so far don't seem so great:

  • Load the association anxiously, potentially loading a lot of unnecessary data.
  • Call Hibernate.initialize(myObject.getAssociation());

    - This means I have to iterate over associations to initialize them (I think), and just the fact that I have to do this kind of makes lazy loading less tidy
  • Using a Spring filter to keep the session open in views, but I doubt it's a good idea to do this?

I tried to use @Transactional

in his service, but no luck. This makes sense because I am trying to access data that has not yet been loaded after my service method returns. Ideally I would like to be able to access any association from my view. I guess the downside to initializing associations in my service is that I have to explicitly define what data I need, but that depends on the context (controller) in which the service is being used. I'm not sure if I can do this in my controller without losing the abstraction that the DBAL layer provides. I hope this makes sense. Anyway, it would be great if I didn't have to always explicitly define what data I want to be available for my view, but just let that view do something. If this is not possible,I'm just looking for the most elegant solution to the problem.

Below is my code.

View

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<h1><c:out value="${company.name}" /> (ID: <c:out value="${company.id}" />)</h1>

<c:forEach var="acknowledgement" items="${company.acknowledgements}">
    <p><c:out value="${acknowledgement.name}" /></p>
</c:forEach>

      

controller

@Controller
public class ProfileController {
    @Autowired
    private CompanyService companyService;

    @RequestMapping("/profile/view/{id}")
    public String view(Model model, @PathVariable int id) {
        Company company = this.companyService.get(id);
        model.addAttribute("company", company);

        return "viewCompanyProfile";
    }
}

      

Service

@Service
public class CompanyServiceImpl implements CompanyService {
    @Autowired
    private CompanyDao companyDao;

    @Override
    public Company get(int id) {
        return this.companyDao.get(id);
    }
}

      

DAO

@Repository
@Transactional
public class CompanyDaoImpl implements CompanyDao {
    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public Company get(int id) {
        return (Company) this.sessionFactory.getCurrentSession().get(Company.class, id);
    }
}

      

Company

@Entity
@Table(name = "company")
public class Company {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    // Other fields here

    @ManyToMany
    @JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id"))
    private Set<Acknowledgement> acknowledgements;

    public Set<Acknowledgement> getAcknowledgements() {
        return acknowledgements;
    }

    public void setAcknowledgements(Set<Acknowledgement> acknowledgements) {
        this.acknowledgements = acknowledgements;
    }

    // Other getters and setters here
}

      

Confirmation object

@Entity
public class Acknowledgement {
    @Id
    private int id;

    // Other fields + getters and setters here

}

      

mvc-dispatcher-servlet.xml (part of it)

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan">
            <list>
                <value>com.mysite.company.entity</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <tx:annotation-driven />

      

Thanks in advance!

+3


source to share


5 answers


The simplest and most transparent solution is the OSIV template. As far as I'm sure you know, there is a lot of discussion about this (anti) pattern and alternatives both on this site and elsewhere, so no need to jump to that anymore. For example:

Why is Hibernate Open Session in View considered bad practice?

However, in my opinion, not all OSIV criticisms are completely accurate (for example, the transaction won't execute until your view is rendered? Really? If you are using a Spring implementation, then fooobar.com/questions/480142/ .. . )



Also, keep in mind that JPA 2.1 introduced the concept of Fetch Graphs, which gives you more control over what is loaded. I haven't tried it yet, but maybe this is, if finally, a solution to this long term problem!

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

+2


source


As @ Predrag Maric said differently initialized write methods for entity associations can be an option.

OpenSessionInView is the discussed pattern, which might be the solution in your case.

Another option is to install

<prop key="hibernate.enable_lazy_load_no_trans">true</prop>



properties. It is designed to solve the problem org.hibernate.LazyInitializationException

and is available since hibernate 4.1.6.

So what does this property do? It simply signals to Hibernate that it should open a new session if the session established inside the current uninitialized proxy is closed. You should be aware that if you have any other open session that is also used to manage ongoing transactions, this newly opened session will be different and it may not participate in the current transaction unless it is JTA. Because of this side effect of TX you must be careful with possible side effects in your system.

Find more information here: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions and   Solve the Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans

+10


source


My vote goes to Hibernate.initialize(myObject.getAssociation())

the service layer (which also means it @Transactional

should be moved from DAO to service methods)

IMHO, the service methods should return all the data the caller needs. If the caller wants to display some association in the view, the service is responsible for delivering that data. This means that you can have multiple methods that obviously do the same (return Company

instance), but different associations can be obtained depending on the caller.

In one project, we had a configuration class that contained information about which associations should be selected, and we had a single service method that also took that class as a parameter. This approach means we only have one method that is flexible enough to support all callers.

@Override
public Company get(int id, FetchConfig fc) {
    Company result = this.companyDao.get(id);
    if (fc.isFetchAssociation1()) {
        Hibernate.initialize(result.getAssociation1());
    }
    ...
    return result;
}

      

+1


source


For anyone looking for a solution, this is how I solved the problem.

As Alan Hay pointed out, JPA 2.1+ supports entity diagrams which ultimately solves my problem. To use it, I change my project to use a class javax.persistence.EntityManager

instead SessionFactory

, which would then pass the current session to me. This is how I configured it in my Servlet Manager config (some things are excluded):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="java:/comp/env/jdbc/postgres" expected-type="javax.sql.DataSource"/>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="dk.better.company.entity, dk.better.user.entity" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven />
</beans>

      

Below is an example of a DAO class.

@Transactional
public class CompanyDaoImpl implements CompanyDao {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public Company get(int id) {
        EntityGraph<Company> entityGraph = this.entityManager.createEntityGraph(Company.class);
        entityGraph.addAttributeNodes("acknowledgements");

        Map<String, Object> hints = new HashMap<String, Object>();
        hints.put("javax.persistence.loadgraph", entityGraph);

        return this.entityManager.find(Company.class, id, hints);
    }
}

      

I found that if I didn't use @PersistenceContext

, but Autowired

instead, then the database session was not closed when the view was rendered, and data updates were not reflected in subsequent requests.

For more information on entity graphs, I recommend you read the following articles:

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html

+1


source


A trigger at the service level requires lazy loading of the Set size method.

Instruments:

public class HibernateUtil {
    /**
     * Lazy = true when the trigger size method is equal to lazy = false (load all attached)
     */
    public static void triggerSize(Collection collection) {
        if (collection != null) {
            collection.size();
        }
    }
}

      

in your service method:

Apple apple = appleDao.findById('xxx');
HibernateUtil.triggerSize(apple.getColorSet());
return apple;

      

then use apple

in controller, it's ok!

0


source







All Articles