【问题标题】:How spring transaction works in this example and how to resolve LazyInitializationException?这个例子中spring事务是如何工作的以及如何解决LazyInitializationException?
【发布时间】:2018-04-26 18:45:57
【问题描述】:

我收到 org.hibernate.LazyInitializationException 异常。我所知道的是,问题是因为我在用户实体中懒惰地获取配置文件对象并且在代理对象被初始化之前会话已关闭。

执行 findAll() 方法后会话是否关闭?是否有任何其他选项可以执行 findAll() 方法并在同一个事务中解析,而不是在 findAll() 方法中解析?

我只想知道spring事务在service的findAll方法和UserUtils类的稍后parse方法被调用时是如何工作的?

我还发现在 @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)

【问题讨论】:

  • 您可以发布您的 UserRepository 代码吗?
  • @locus2k 完成!!!
  • 谢谢。添加了一些有用的建议。

标签: spring hibernate jpa spring-data-jpa


【解决方案1】:

执行 findAll() 方法后会话是否关闭?

是的。默认情况下,对存储库的任何操作都是原子的。

是否有任何其他选项可以执行 findAll() 方法并在其中解析 同一个事务而不是在 findAll() 方法中解析?

是的。如果您在方法周围有 @Transactional 执行这两个操作。在该方法返回之前,事务将保持打开状态。

我只想知道findAll方法时spring事务是如何工作的 从服务中调用 UserUtils 类的后续解析方法?

您有多种选择。

  1. 在控制器上添加 @Transactional 注释 getListOfUsers 应该可以工作。

  2. 但是,您可能会考虑使用一种服务方法来搜索所有用户并创建响应。您可能希望将 @Transactional 放在此方法上。

  3. 第三种选择是创建一个命名查询,明确请求作为查询的一部分急切获取配置文件。

我会说第二个或第三个选项比第一个更可取。

【讨论】:

  • 您对 Transactional annotation 的传播有任何想法吗?不能用来解决这个问题吗?
  • 传播是可选的。有时您想参与已经存在的交易或创建一个全新的交易,即使该交易已经存在。在你的情况下,默认就可以了。
  • 同样,我想参与已经存在的交易。不会帮我吗? :D
  • 如果你有一个 GET 映射,那么你有一个 HTTP 请求进来。因此你不在一个事务中。如果您想在事务期间进行其他活动,那么您可能希望使用选项 2,并将您想做的所有事情都放在服务方法中。
  • 这里是关于不同传播模型的信息:docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
【解决方案2】:

我很好奇为什么你有一个 OneToOne on lazy fetch?典型的 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);
});

【讨论】:

  • 我在使用 HQL 时使用延迟获取只是为了避免内部连接。例如:如果我在 Profile 实体中映射了许多其他对象,那么它可能会抛出错误,因为 mysql 在获取实体时只能使用 61 个连接。
【解决方案3】:

解决此问题的最佳方法是直接从数据库中获取List&lt;UserResponseDTO&gt;,而无需先获取实体然后再对其进行转换。

在您的存储库中创建一个方法:

@Query("select new com.company.UserResponseDTO(u.id, u.name, u.profile.name, u.profile.createdDate) from User u")
List<UserResponseDTO> findAllAsDTO();

确保您的 UserResponseDTO 具有适当的构造函数,该构造函数按此顺序获取此参数。

这是最有效的方法。它通过不获取实体来节省内存,实体需要额外的内存来进行脏检查。这也更具可扩展性,因为如果有人将属性添加到用户或配置文件实体,他们不会影响此查询。最后,这也是要编写的更少代码。

我推荐这种方法。

这种技术称为JPQL Constructor Expression

【讨论】:

    【解决方案4】:

    您正在尝试在事务之外延迟加载 profile - 因此出现异常。

    要解决此问题,您可以将您的实体映射到 UserService 实现中的 DTO。在您的情况下,这似乎是最合适的解决方案。

    您也可以急切地获取profile 或将控制器方法标记为@Trasnational,但这似乎不是一个好主意,因为控制器不包含任何业务逻辑。

    【讨论】:

      猜你喜欢
      • 2023-01-24
      • 2014-12-17
      • 2018-10-13
      • 2019-09-30
      • 2012-09-28
      • 2020-05-26
      • 2021-10-05
      • 2017-11-26
      相关资源
      最近更新 更多