我收到 org.hibernate.LazyInitializationException 异常。我所知道的是,问题是由于我在懒惰的用户实体中获取了配置文件对象,并且在代理对象初始化之前会话已关闭。
执行findAll()方法后会话是否关闭?是否有任何其他选项来执行findAll()方法并在同一事务中解析而不是解析findAll()方法内部?
我只想知道当调用来自服务的findAll方法和稍后调用UserUtils类的方法时spring事务是如何工作的?
我还发现在@Transactional注释中使用传播会有所帮助。会吗?请说清楚。
现在让我们看一些代码。
User.java
package com.technep.test.entity;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
@Table(name="user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String fatherName;
private String motherName;
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="user_profile_id")
private Profile profile;
}
Profile.java
@Entity
@Table(name="profile")
@Getter
@Setter
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name="profile_name")
private String name;
@Column(name="created_date")
private Date createdDate;
@Column(name="last_modified_date")
private Date lastmodifiedDate;
}
UserController.java
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping(value = "/api/users")
public ResponseEntity<List<UserResponseDTO>> getListOfUsers(){
List<User> users = userService.findAll();
List<UserResponseDTO> responseDTOs =UserUtils.parseUserToDTO(users);
return new ResponseEntity<List<UserResponseDTO>>(responseDTOs,HttpStatus.OK);
}
}
UserService.java
public interface UserService {
User findById(Integer id);
List<User> findAll();
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer>{
}
UserServiceImpl.java
@Service
@Transactional
public class UserServiceImpl implements UserService{
@PersistenceContext
private EntityManager entityManager;
@Autowired
private UserRepository repository;
@Override
public User findById(Integer id) {
return repository.findOne(id);
}
@Override
public List<User> findAll() {
return repository.findAll();
}
}
UserUtils.java
public class UserUtils {
public static List<UserResponseDTO> parseUserToDTO(List<User> users) {
List<UserResponseDTO> responseDTOs = new ArrayList<>();
users.forEach(user -> {
UserResponseDTO responseDTO = new UserResponseDTO();
responseDTO.setId(user.getId());
responseDTO.setName(user.getName());
responseDTO.setProfileName(user.getProfile().getName());
responseDTO.setProfileCreatedDate(user.getProfile().getCreatedDate());
responseDTOs.add(responseDTO);
});
return responseDTOs;
}
}
例外是:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:147)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:260)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:68)
at com.technep.test.entity.Profile_$$_jvst12b_0.getName(Profile_$$_jvst12b_0.java)
at com.technep.test.utils.UserUtils.lambda$parseUserToDTO$0(UserUtils.java:27)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at com.technep.test.utils.UserUtils.parseUserToDTO(UserUtils.java:23)
at com.technep.test.controller.UserController.getListOfUsers(UserController.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:650)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
答案 0 :(得分:1)
执行findAll()方法后会话是否关闭?
是。默认情况下,存储库上的任何操作都是原子操作。
是否有任何其他选项可以执行findAll()方法并在其中解析 一个相同的事务,而不是解析findAll()方法?
是。如果你有两个动作的方法围绕@Transactional。交易将保持开放,直到该方法返回。
我只想知道findAll方法时spring事务的工作原理 从服务和后来的UserUtils类的解析方法调用?
您有几种选择。
在控制器上添加@Transactional
注释getListOfUsers应该有效。
但是,您可能会考虑使用一种服务方法来搜索所有用户并创建响应。您可能希望将@Transactional
放在此方法上。
第三个选项是创建一个命名查询,该查询显式请求在查询中急切地获取配置文件。
我会说第二个或第三个选项比第一个更好。
答案 1 :(得分:1)
我很好奇为什么你有一个关于懒人提取的OneToOne?典型的OneToOnes不是这样设置的。延迟加载是一种设计模式,用于在可能的情况下推迟对象的初始化,并且通常在获取列表时使用。使用Hibernate时,通常在用户尝试获取资源时关闭事务。我建议删除Lazy Fetch并尝试这样做。
如果您在这里找不到其他有用的建议,可以试试。
您实际上可以在UserRepository
中撰写查询以获取您的个人资料,如下所示:
@Query("SELECT user FROM User user JOIN FETCH user.profile")
List<User> findAll();
这将覆盖内置的findAll,并使用已经急切为您提取的用户个人资料获取列表。
您可以做的另一件事是创建一个UserProfileRepository并在获得用户之后获取该配置文件:
public interface UserProfileRepository extends JpaRepository<UserProfile, Integer>{
Profile findById(Integer id);
}
然后在您的DTO中,您可以执行以下操作:
users.forEach(user -> {
UserResponseDTO responseDTO = new UserResponseDTO();
responseDTO.setId(user.getId());
responseDTO.setName(user.getName());
Profile profile = profileRepository.findById(user.getProfileId());
responseDTO.setProfileName(profile.getName());
responseDTO.setProfileCreatedDate(profile.getCreatedDate());
responseDTOs.add(responseDTO);
});
答案 2 :(得分:1)
解决此问题的最佳方法是直接从数据库中获取List<UserResponseDTO>
,而无需先获取实体然后转换它们。
在您的资源库中创建一个方法:
@Query("select new com.company.UserResponseDTO(u.id, u.name, u.profile.name, u.profile.createdDate) from User u")
List<UserResponseDTO> findAllAsDTO();
确保您的UserResponseDTO具有适当的构造函数,该构造函数按此顺序接受此参数。
这是最有效的方式。它通过不获取实体来节省内存,这些实体将额外的内存用于脏检查。此外,这更具可伸缩性,因为如果有人向User或Profile实体添加属性,则它们不会影响此查询。最后,这也是编写的代码较少。
我推荐这种方法。
答案 3 :(得分:0)
您正在尝试在事务之外延迟加载profile
- 因此是例外。
要解决此问题,您可以将实体映射到UserService
实施中的DTO。在您的情况下,这似乎是最合适的解决方案。
您也可以急切地获取profile
或将控制器方法标记为@Trasnational
,但由于控制器不包含任何业务逻辑,因此它似乎不是一个好主意。