Reputation: 3030
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.
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.
Upvotes: 2
Views: 1139
Reputation: 4071
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.
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.
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
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?
Upvotes: 5
Reputation: 774
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
Upvotes: 2