2

I created a user management service based on springboot.

The user will have a list of attachments, so the relationship of the user and attachment is OneToMany.

I ignored the insert logic here since my question is about the lazyload and when the entitymanager is opened and closed. Below is the entity, controller, service, dao, repository related code.

Entity

@Entity
@Table(name="User")
public class UserInfoEntity {

 private long id;
 private String mail;

 @OneToMany(fetch = FetchType.LAZY, mappedBy = "userInfoEntity", cascade = CascadeType.ALL)
 private List<UserAttachmentEntity> attachmentList = new ArrayList<>();

}


@Entity
@Table(name = "ATTACHMENT")
public class UserAttachmentEntity {
    private long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="userRequestId")
    private UserInfoEntity userInfoEntity;

}

Service

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;


    @Override
    @Transactional
    public void save(UserInfoEntity userInfoEntity) throws RestException {
        userDao.save(userInfoEntity);
    }



    @Override
    // I did set @Transactional here
    public UserInfoEntity findByMail(String mail) {
        UserInfoEntity userInfoEntity = userDao.findByMail(mail);
        return userInfoEntity;
    }
}

DAO

@Service
public class UserDaoImpl implements UserDao {

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private UserInfoEntityRepository userInfoRepository;

    @Override
    public UserInfoEntity findByMail(String mail) {
        return userInfoRepository.findFirstByMailOrderByIdDesc(mail);
    }
}

Repository

@Repository
public interface UserInfoEntityRepository extends JpaRepository<UserInfoEntity, Integer> {
    UserInfoEntity findFirstByMailOrderByIdDesc(String mail);
}

Controller

@Controller
@RequestMapping(value = "/user")
public class UserController {
    @RequestMapping(value = "load", method = RequestMethod.POST)
    @ResponseBody
    public UserInfoEntity load(UserInfoEntity userInfoEntityInput, HttpServletRequest request) throws Exception {
        UserInfoEntity userInfoEntity = userService.findByMail(userInfoEntityInput.getMail());
        System.out.println(userInfoEntity.getAttachmentList());
        return userInfoEntity;
    }
}

After I test the load method in controller, I found that even I set the

@OneToMany(fetch = FetchType.LAZY,...) in UserInfoEntity.

I can still call userInfoEntity.getAttachmentList() in controller. (I can see the select * from attachment query was printed there)

The answer of the post lazyinitializationexception-in-spring-data-jpa said that you need to fetch the lazy data while you are inside of a transaction.

But I did not set the @Transaction in the findByMail method in service.

And I remember that I also met this exception a couple of days ago. But now I can load the lazy data successfully in controller.

I mainly have below questions.

  1. When the entitymanager is opened and closed? (is it opened in service or dao?)
  2. Why the lazy data can be loaded in controller?
  3. Is the entity manager thread-safe? (I googled, but not find useful answer)
  4. Is the entity manager singleton? ( in above code, I inject the entity manager into dao, although I did not use it, I used spring data, I can inject the entity manager to service, controller, find the hashcode is different)

Thanks in advance. I wrote this question in company, I was not allowed to push any code to github in company. If I can, I think it will be more convenient for you since the spring boot project with h2 database is very easy to setup locally.

liam xu
  • 2,582
  • 7
  • 31
  • 52

2 Answers2

5

Spring Boot JPA Opens Session in View by default

If you go along the Appendix A. Common application properties, you will find by default Spring boot defines

spring.jpa.open-in-view=true

That actually register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request. This actually leads to confusion, See this issue reported by the community.

When the EntityManager is opened and closed?

Basically, an EntityManager opens a transaction where @Transaction annotation is declared or in your case it is opened by the OpenEntityManagerViewInterceptor on every request and that stays open before the response is made.

Is the entity manager thread-safe?

NO. Its not threadsafe, but the EntityManagerFactory is. And EntityManager is obtained from EntityManagerFatory. Hence an EntityManager is inexpensive and expected to be used once for a single unit of work. Where An EntityManagerFactory is typically created at application initialization time and closed at the application end. See Hibernate doc for details about Obtaining an EntityManager in a Java SE environment

Is the entity manager singleton?

No. EntityManager is an interface, and what gets injected in the spring bean when you autowire, is not the entity manager itself but a context-aware proxy that will delegate to a concrete entity manager at runtime. Usually, the concrete class used for the proxy is SharedEntityManagerInvocationHandler

For more detail explanation about how the whole JPA transaction go around. I recommend reading this How Does Spring @Transactional Really Work?

Shafin Mahmud
  • 2,977
  • 1
  • 18
  • 29
  • Thanks. The entity manager is not thread-safe, so it's not correct to inject the entitymanager by @PersistenceContext private EntityManager entityManager; ? – liam xu Mar 07 '19 at 05:32
  • 1
    No. Its always safe to inject `EntityManager` by `@PersistenceContext`. cause note that spring bind the `proxy`. not the real object. – Shafin Mahmud Mar 07 '19 at 09:05
2

In Spring Boot the Open Session In View is enabled by default which means that you don't have to use @Transaction in your code unless it is required by your app's logic.

However, note that the enabled OSIV is considered as bad practice - the detailed debate about this topic can be found at https://stackoverflow.com/a/37526397/3750108

toucheqt
  • 525
  • 2
  • 6
  • 13