带有spring-security AuditorAware的spring-data-jpa应用程序中的StackOverflowException

Rob*_*ert 2 spring-security spring-data spring-data-jpa

我的春季后端有一个非常讨厌的StackOverflowException,需要帮助。这将不容易解决。我真的希望在这里找到一些帮助。

我后端的大部分工作。我可以在我的REST接口中查询模型,它们可以通过spring-hateoas,GET,PUT和POST操作很好地返回。但是有一个例外:当我尝试更新现有的时DelegationModel,便遇到了无尽的StackOverflowException。

这是我的DelegetionModel.java课。请标记,该委托模型实际上没有任何带@CreatedBy注释的属性!

@Entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor(suppressConstructorProperties = true)  //BUGFIX:  https://jira.spring.io/browse/DATAREST-884
@EntityListeners(AuditingEntityListener.class)  // this is necessary so that UpdatedAt and CreatedAt are handled.
@Table(name = "delegations")
public class DelegationModel {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public Long id;

  /** Area that this delegation is in */
  @NonNull
  @NotNull
  @ManyToOne
  public AreaModel area;

  /** reference to delegee that delegated his vote */
  @NonNull
  @NotNull
  @ManyToOne
  public UserModel fromUser;

  /** reference to proxy that receives the delegation */
  @NonNull
  @NotNull
  @ManyToOne
  public UserModel toProxy;

  @CreatedDate
  @NotNull
  public Date createdAt = new Date();

  @LastModifiedDate
  @NotNull
  public Date updatedAt = new Date();

}
Run Code Online (Sandbox Code Playgroud)

Spring-data-jpa文档中所述,我实现了必要的AuditorAware接口,该接口从SQL DB加载UserModel。我希望只对具有以批注字段的模型调用此AuditorAware接口@CreatedBy

@Component
public class LiquidoAuditorAware implements AuditorAware<UserModel> {
  Logger log = LoggerFactory.getLogger(this.getClass());  // Simple Logging Facade 4 Java

  @Autowired
  UserRepo userRepo;    

  @Override
  public UserModel getCurrentAuditor() {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication == null || !authentication.isAuthenticated()) {
        log.warn("Cannot getCurrentAuditor. No one is currently authenticated");
        return null;
      }
      User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
      UserModel currentlyLoggedInUser = userRepo.findByEmail(principal.getUsername());   // <<<<======= (!)
      return currentlyLoggedInUser;
    } catch (Exception e) {
      log.error("Cannot getCurrentAuditor: "+e);
      return null;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,我在中更新了一个SubjectModel UserRestController。功能“ Scrum用户故事”在这里是:

作为用户,我希望能够存储一个代表团,以便我可以将投票权转发给我的代理人。

@RestController
@RequestMapping("/liquido/v2/users")
public class UserRestController {

  [...]

  @RequestMapping(value = "/saveProxy", method = PUT, consumes="application/json")
  @ResponseStatus(HttpStatus.CREATED)
  public @ResponseBody String saveProxy(
                      @RequestBody Resource<DelegationModel> delegationResource,
                      //PersistentEntityResourceAssembler resourceAssembler,
                      Principal principal)            throws BindException
  {
    [...]
    DelegationModel result = delegationRepo.save(existingDelegation);
    [...]
  }

  [...]         

}
Run Code Online (Sandbox Code Playgroud)

由于某种原因(我看不到),实际上调用了上面的AuditorAware实现。现在的问题是,我的LqiuidoAuditorAware实现在无限循环中一次又一次地调用。似乎在LiquidoAuditorAware.java中对UserModel的查询再次调用了LiquidoAuditorAware。(这很不寻常,因为那只是从数据库读取的操作。)

这是完整的ThreadDump作为要点

所有代码都可以在此github存储库中找到

我真的很想在这里提供任何帮助。我在黑暗中搜索:-)

Oli*_*ohm 5

您看到的行为的原因是,该AuditorAware实现是从JPA @PrePersist/ @PreUpdate回调中调用的。现在,您通过调用发出查询findByEmail(…),这再次触发了脏检测,这又导致触发刷新,从而调用了回调。

推荐的解决方法是UserModel在Spring Security User实现内部保留一个实例(通过UserDetailsService在身份验证时查找实例时对其进行查找),这样就不需要额外的数据库查询。

另一个(不建议使用)的解决方法是将a注入EntityManagerAuditorAware实现中,setFlushMode(FlushModeType.COMMIT)在查询执行之前调用,然后将其重置为FlushModeType.AUTO之后,以便不会为查询执行触发刷新。