11

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!

Dmitry Senkovich
  • 5,521
  • 8
  • 37
  • 74

6 Answers6

15

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/

v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • Thank you, now I see the problem in initialization the entity object being loaded lazy. But how can I fix the initialization? – Dmitry Senkovich Mar 06 '17 at 18:50
  • But why can't I use Transactional in controller method? What is the difference? But let me try your suggestion. – Dmitry Senkovich Mar 06 '17 at 19:06
  • @DmitrySenkovich You are welcome. I have added some additional explanations. – v.ladynev Mar 06 '17 at 19:16
  • Thank very much one more time. This is the technical explanation I was looking for not the one from architecture point of view I saw before. – Dmitry Senkovich Mar 06 '17 at 19:21
  • @v.ladynev i havn't used Transactional but still i am getting this error. Please give some suggestions. – Salman S Nov 15 '18 at 11:18
  • 1
    @SalmanS This exception just means that you don't have an open `Session` or `Persistent Context` when you call a method of a persistent object. – v.ladynev Nov 15 '18 at 12:16
  • @v.ladynev i have used the call to database in a CompletableFuture i.e thenCompose ... but outside the CompletableFuture i am good to call to database without error... any help is appreciated.. – Salman S Nov 16 '18 at 09:16
  • 1
    @SalmanS `Session` is not thread safe. So you can't use it in the concurrency environment. How Hibernate gets the current session is controlled by properties. One of the way is to use `ThreadLocal` to bound the session to a thread. Probably that is the reason of the issue. https://stackoverflow.com/questions/8046662/hibernate-opensession-vs-getcurrentsession – v.ladynev Nov 16 '18 at 10:37
2

As @v.ladynev briefly explained, your issue was that you wanted to initialize a lazy relation outside of the persistence context.

I wrote an article about this, you might find it helpful: https://arnoldgalovics.com/lazyinitializationexception-demystified/

momolechat
  • 155
  • 11
Arnold Galovics
  • 3,246
  • 3
  • 22
  • 33
  • Thanks, I will read it! But the most interesting part here for me was that Spring doesn't any proxy for controllers adding transaction behaviour. – Dmitry Senkovich Mar 07 '17 at 08:29
  • There are ways to do it, for example you can use a Filter which automatically adds transactional context for each request. The reason it's not a good idea is because a Transaction should represent a unit of work in terms of business logic and not in terms of handling a request. – Arnold Galovics Mar 07 '17 at 08:32
1

For quick solutions despite of performance issues use @transactional in your service Sample:

@Transactional
public TPage<ProjectDto> getAllPageable(Pageable pageable) {
    Page<Project> data = projectRepository.findAll(pageable);
    TPage<ProjectDto> response = new TPage<>();
    response.setStat(data, Arrays.asList(modelMapper.map(data.getContent(), ProjectDto[].class)));
    return response;
}

it will get user details for project manager in the second query. For more advanced solution, you should read the blog post in the @galovics answer.

alphkn
  • 11
  • 2
1

I was also facing the same error while running my springBoot App.

What is the real issue here?

  • Please check have you autowired the repository at controller level
  • If first step is correct then please check where ever you have autowired your JPA repository , it should be a part of @Transactional code.
  • If not please add @Transactional annotation.It will solve your issue.
rahulnikhare
  • 1,362
  • 1
  • 18
  • 25
0

I used below to fix sessionFactory.getObject().getCurrentSession() Create query and get required object

Shahid Hussain Abbasi
  • 2,508
  • 16
  • 10
0

I was getting this error:

Method threw 'org.hibernate.LazyInitializationException' exception.

This is because currently there is no session present. Hibernate opens a session and closes it, but for "lazy = true" or "fetch = FetchType.LAZY" such fields are populated by proxies. When you try to find the value for such a field, it will attempt to go to the database using the active session to retrieve the data. If no such session can be found, you get this exception.

You can fix it using "lazy=false" or check whether you have used @Transcational properly (try to use this in your service layer than your data access layer), you can also use

@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)

OR

@Transactional

CodingBee
  • 1,011
  • 11
  • 8