首页 > 解决方案 > Hibernate LAZY 加载和 spring 的 UserDetails

问题描述

我有一个无状态的 REST 后端。所以没有 HTML 视图。只是 JSON 和 REST 端点。使用 Json Web 令牌完成身份验证。客户端在每个请求中发送一个 JWT。我的后端从这个 JWT 中的主题声明中获取用户的电子邮件。然后它从数据库中加载 UserModelclass LiquidoUserDetailsService implements UserDetailsService { ...}

每个用户都是团队的一部分。但是团队是一个很大的实体,里面有很多信息。所以团队只会在必要时懒惰地加载:

用户模型.java

@Entity
@Table(name = "users")
public class UserModel extends BaseModel {
  @NotNull
  @NonNull
  @Column(unique = true)
  public String email;
  @ManyToOne(fetch = FetchType.LAZY)  // only load team info (with all info) if required
  public TeamModel team;

  [...]
}

现在我有一个服务应该返回当前用户的团队:

团队服务.java

@PreAuthorize(HAS_ROLE_USER)
@RequestMapping("/getOwnTeam")
@Transactional                          // [1] 
public TeamModel getOwnTeam() {
  // Get currently logged in user (that was loaded with info from JWT)
  Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
  LiquidoAuthUser authUser = (LiquidoAuthUser)authentication.getPrincipal();
  // LiquidoAuthUser is the Adapter betwen spring's User and my USerModel
  UserModel currentUser = authUser.getLiquidoUserModel()    

  TeamModel team = currentUser.getTeam()     // <=== [2] throws LazyInitializationException

  return team
}

现在我想我知道问题出在哪里了。但我还没有一个干净的解决方案。

我的 UserModel 已加载class LiquidoUserDetailsService implements UserDetailsService但是这很早就发生在过滤器中,当 HTTP 请求被处理时。因为它接缝@Transaction在我的TeamService课上当时还没有开始。

然后当代码进入该getOwnTeam()方法时,将启动一个事务 [1]。但是在那里我不能再懒加载用户的团队了。[2]

我如何为我的用户和团队建模,以便

  1. 团队数据仅在必要时加载
  2. 必要时我可以手动加载数据

标签: springhibernatespring-data-jpa

解决方案


如果您需要不同的负载启动,您可以使用:

  1. 查询时的原生sql
  2. jpql 与 join fetch 之类的结构
  3. 实体图 ( https://www.baeldung.com/jpa-entity-graph ) 使用这种方式加载的主要好处是对数据库的单个请求。您可以阅读更多https://thorben-janssen.com/lazyinitializationexception/

您的对象处于分离状态 - 例如,这是 LazyInitializationException 的原因(您将其移动到其他状态以加载您的对象)

entityManager.merge(deatachedEntity);

推荐阅读