Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LazyInitializationException trying to get lazy initialized instance

I see the following exception message in my IDE when I try to get lazy initialized entity (I can't find where it is stored in the proxy entity so I can't provide the whole stack trace for this exception):

Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate com.epam.spring.core.domain.UserAccount_$$_jvste6b_4.toString()

Here is a stack trace I get right after I try to access a field of the lazy initialized entity I want to use:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)

    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)

    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)

    at com.epam.spring.core.domain.UserAccount_$$_jvstfc9_4.getMoney(UserAccount_$$_jvstfc9_4.java)

    at com.epam.spring.core.web.rest.controller.BookingController.refill(BookingController.java:128) 

I'm using Spring Data, configured JpaTransactionManager, database is MySql, ORM provider is Hibernate 4. Annotation @EnableTransactionManagement is on, @Transactional was put everywhere I could imagine but nothing works.

Here is a relation:

@Entity
public class User extends DomainObject implements Serializable {

    ..

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "user_fk")
    private UserAccount userAccount;

    ..

@Entity
public class UserAccount extends DomainObject {

    ..

    @OneToOne(mappedBy = "userAccount")
    private User user;

    ..

.. a piece of configuration:

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(env.getRequiredProperty(PROP_NAME_DATABASE_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(PROP_NAME_DATABASE_URL));
        dataSource.setUsername(env.getRequiredProperty(PROP_NAME_DATABASE_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PROP_NAME_DATABASE_PASSWORD));
        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROP_ENTITYMANAGER_PACKAGES_TO_SCAN));
        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());            
        return entityManagerFactoryBean;
     }

    @Bean
    public JpaTransactionManager transactionManager(@Autowired DataSource dataSource,
                                                    @Autowired EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
        jpaTransactionManager.setDataSource(dataSource);

        return jpaTransactionManager;
    }

.. and this is how I want to retrieve UserAccount:

    @RequestMapping(...)
    @Transactional()
    public void refill(@RequestParam Long userId, @RequestParam Long amount) {
        User user = userService.getById(userId);
        UserAccount userAccount = user.getUserAccount();
        userAccount.setMoney(userAccount.getMoney() + amount);
    }

Hibernate version is 4.3.8.Final, Spring Data 1.3.4.RELEASE and MySql connector 5.1.29.

Please, ask me if something else is needed. Thank you in advance!

like image 733
Dmitry Senkovich Avatar asked Mar 06 '17 18:03

Dmitry Senkovich


1 Answers

Firstly, you should understand that the root of the problem is not a transaction. We have a transaction and a persistent context (session). With @Transactional annotation Spring creates a transaction and opens persistent context. After method is invoked a persistent context becomes closed.

When you call a user.getUserAccount() you have a proxy class that wraps UserAccount (if you don't load UserAccount with User). So when a persistent context is closed, you have a LazyInitializationException during call of any method of UserAccount, for example user.getUserAccount().toString().

@Transactional working only on the userService level, in your case. To get @Transactional work, it is not enough to put the @Transactional annotation on a method. You need to get an object of a class with the method from a Spring Context. So to update money you can use another service method, for example updateMoney(userId, amount).

If you want to use @Transactional on the controller method you need to get a controller from the Spring Context. And Spring should understand, that it should wrap every @Transactional method with a special method to open and close a persistent context. Other way is to use Session Per Request Anti pattern. You will need to add a special HTTP filter.

https://vladmihalcea.com/the-open-session-in-view-anti-pattern/

like image 140
v.ladynev Avatar answered Oct 20 '22 00:10

v.ladynev