Spring 存储库并不总是抛出 DataIntegrityViolationException
Spring repository not always throwing DataIntegrityViolationException
我正在用 Spring 制作 REST API,但我无法对其进行单元测试。
我写了一个端点来更新用户组,当我在我的前端创建一个具有重复名称的组时,它确实发送了 409 冲突 (unique=true)。但是,当我进行单元测试时却没有。我发现在单元测试结束时添加这一行 groupRepository.findAll().forEach(g -> System.out.println(g.getName()));
确实会抛出 409.
端点:
@Override
public ResponseEntity<Object> update(@ApiParam(value = "form object to add to the store", required = true) @Valid @RequestBody FormGroupDTO group, @ApiParam(value = "Id of the form that needs to be updated", required = true) @PathVariable("groupId") Long groupId, @ApiParam(value = "token to be passed as a header", required = true) @RequestHeader(value = "token", required = true) String token) {
String name = JWTutils.getEmailInToken(token);
if(name == null) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
User user = userRepository.findByEmail(name);
if(user == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
FormGroup groupModel = groupRepository.findByIdAndAdmin(groupId, user);
if(groupModel == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
if(group.getMembers().stream().filter(m -> m.getRole() == UserFormGroupRole.ADMIN).toArray().length == 0){
return new ResponseEntity<>(new ValidationErrorDTO("noAdmin", "MEMBER.NOADMIN"), HttpStatus.BAD_REQUEST);
}
// Get users
groupModel.getUserFormGroups().clear();
for(MemberDTO member : group.getMembers()){
User u = userRepository.findByEmail(member.getEmail());
if(u == null){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTEXIST"), HttpStatus.BAD_REQUEST);
}
if(u.getRole() == UserRole.USER){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTPRIVILEGED"), HttpStatus.BAD_REQUEST);
}
UserFormGroup ufg = userFormGroupRepository.findByUserAndFormGroup(u, groupModel);
if(ufg == null){
groupModel.getUserFormGroups().add(new UserFormGroup(u, groupModel, member.getRole()));
} else{
ufg.setRole(member.getRole());
groupModel.getUserFormGroups().add(ufg);
}
}
groupModel.setName(group.getName());
try{
groupRepository.save(groupModel);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (DataIntegrityViolationException e){
System.out.println(e.getMessage());
System.out.println(e.getClass());
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
}
我的单元测试:
@Test
public void updateGroupNameAlreadyInUse() throws Exception {
groupRepository.save(new FormGroup("newFormGroup", user));
this.mockMvc.perform(put("/groups/" + group.getId())
.header("token", token)
.content(json(new FormGroupDTO("newFormGroup", group.getUserFormGroups().stream().map(ufg -> new MemberDTO(ufg.getUser().getEmail(), ufg.getRole())).collect(Collectors.toList()))))
.contentType(contentType))
.andExpect(status().isConflict());
}
我的 CrudRepository 的保存功能并不总是抛出 DataIntegrityViolationException。我刚刚意识到,也许我的单元测试的第一行 groupRepository.save(new FormGroup("newFormGroup", user));
可能不会在我的单元测试结束之前执行,并且 findAll
函数会触发它。
简短的故事:您需要在插入测试后手动执行 flush()
。现在让我们详细讨论。我假设您正在使用 ID 生成策略,例如 Sequence 或 UUID 或类似的东西。
有很多事情需要考虑:
同花顺
- FlushMode - 确定 ORM 何时触发 SQL 语句。默认情况下,它在任何
SELECT
语句之前和事务提交之前触发。您获取每条记录名称的解决方案发出一个 SELECT
语句 - 触发所有未决 SQL 语句的刷新。
save()
或 persist()
保证返回一个持久对象。这样的对象必须有一个 ID。一些 ID 生成策略(如 Identity
)需要一个 INSERT
语句来生成 ID。其他人(如 Sequence
、UUID
)- 不要。因此 ORM 可以在不插入记录的情况下获取 ID(它希望尽可能延迟一些优化)。
因此,在进行与 ORM 相关的测试时,您必须在对该数据执行任何操作之前手动调用 flush()
。
交易与会话
当您将事物标记为 @Transactional
时,行为是:
- 查看交易是否已在此线程中打开。
- 如果是 - 什么都不做。
- 如果否 - 创建事务并将其绑定到当前线程(通过 ThreadLocal 变量)。
- 方法完成后 - 检查事务是否由我启动。
- 如果没有 - 什么也不做。
- 如果是 - 提交事务,关闭会话。
我假设你用 @Transactional
标记你的测试。这意味着要测试谁启动会话和事务。存储库只使用已经打开的。然后因为没有提交 - 没有刷新。然后你使用 MockMvc - 在同一个线程中工作。它通过 @Transactional
或 OSIV
也发现交易已经开始。所以事务被重用了。
然后进入您的核心逻辑 - 您正在执行一些 SELECT
语句,这会刷新当前会话中未决的 SQL 语句。所以你原来的 save()
刚刚被冲洗了。
现在,在您的逻辑末尾,您再次执行 save()
,它将 INSERT 语句放入待处理的 SQL 查询中。测试完成后,它只是回滚事务,最终的 INSERT
不会发生。除非.. 你正在做你提到的 SELECT
陈述。
所以最后 - 不要忘记在与 ORM 相关的测试中执行 flush()
和 clear()
。这些方法存在于 Hibernate 的 Session
或 JPA 的 EntityManager
中。前者可以用:SessionFactory#getCurrentSession()
完成,后者可以用:
注入
@PersistenceContext
EntityManager entityManager;
PS:我没有看到任何标有@Transactional
的生产代码。如果不这样做,您可能 运行 会遇到问题。
PPS: this is not a unit test.
我正在用 Spring 制作 REST API,但我无法对其进行单元测试。
我写了一个端点来更新用户组,当我在我的前端创建一个具有重复名称的组时,它确实发送了 409 冲突 (unique=true)。但是,当我进行单元测试时却没有。我发现在单元测试结束时添加这一行 groupRepository.findAll().forEach(g -> System.out.println(g.getName()));
确实会抛出 409.
端点:
@Override
public ResponseEntity<Object> update(@ApiParam(value = "form object to add to the store", required = true) @Valid @RequestBody FormGroupDTO group, @ApiParam(value = "Id of the form that needs to be updated", required = true) @PathVariable("groupId") Long groupId, @ApiParam(value = "token to be passed as a header", required = true) @RequestHeader(value = "token", required = true) String token) {
String name = JWTutils.getEmailInToken(token);
if(name == null) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
User user = userRepository.findByEmail(name);
if(user == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
FormGroup groupModel = groupRepository.findByIdAndAdmin(groupId, user);
if(groupModel == null){
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
}
if(group.getMembers().stream().filter(m -> m.getRole() == UserFormGroupRole.ADMIN).toArray().length == 0){
return new ResponseEntity<>(new ValidationErrorDTO("noAdmin", "MEMBER.NOADMIN"), HttpStatus.BAD_REQUEST);
}
// Get users
groupModel.getUserFormGroups().clear();
for(MemberDTO member : group.getMembers()){
User u = userRepository.findByEmail(member.getEmail());
if(u == null){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTEXIST"), HttpStatus.BAD_REQUEST);
}
if(u.getRole() == UserRole.USER){
return new ResponseEntity<>(new ValidationErrorDTO("notexist", "ADDUSER.NOTPRIVILEGED"), HttpStatus.BAD_REQUEST);
}
UserFormGroup ufg = userFormGroupRepository.findByUserAndFormGroup(u, groupModel);
if(ufg == null){
groupModel.getUserFormGroups().add(new UserFormGroup(u, groupModel, member.getRole()));
} else{
ufg.setRole(member.getRole());
groupModel.getUserFormGroups().add(ufg);
}
}
groupModel.setName(group.getName());
try{
groupRepository.save(groupModel);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (DataIntegrityViolationException e){
System.out.println(e.getMessage());
System.out.println(e.getClass());
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
}
我的单元测试:
@Test
public void updateGroupNameAlreadyInUse() throws Exception {
groupRepository.save(new FormGroup("newFormGroup", user));
this.mockMvc.perform(put("/groups/" + group.getId())
.header("token", token)
.content(json(new FormGroupDTO("newFormGroup", group.getUserFormGroups().stream().map(ufg -> new MemberDTO(ufg.getUser().getEmail(), ufg.getRole())).collect(Collectors.toList()))))
.contentType(contentType))
.andExpect(status().isConflict());
}
我的 CrudRepository 的保存功能并不总是抛出 DataIntegrityViolationException。我刚刚意识到,也许我的单元测试的第一行 groupRepository.save(new FormGroup("newFormGroup", user));
可能不会在我的单元测试结束之前执行,并且 findAll
函数会触发它。
简短的故事:您需要在插入测试后手动执行 flush()
。现在让我们详细讨论。我假设您正在使用 ID 生成策略,例如 Sequence 或 UUID 或类似的东西。
有很多事情需要考虑:
同花顺
- FlushMode - 确定 ORM 何时触发 SQL 语句。默认情况下,它在任何
SELECT
语句之前和事务提交之前触发。您获取每条记录名称的解决方案发出一个SELECT
语句 - 触发所有未决 SQL 语句的刷新。 save()
或persist()
保证返回一个持久对象。这样的对象必须有一个 ID。一些 ID 生成策略(如Identity
)需要一个INSERT
语句来生成 ID。其他人(如Sequence
、UUID
)- 不要。因此 ORM 可以在不插入记录的情况下获取 ID(它希望尽可能延迟一些优化)。
因此,在进行与 ORM 相关的测试时,您必须在对该数据执行任何操作之前手动调用 flush()
。
交易与会话
当您将事物标记为 @Transactional
时,行为是:
- 查看交易是否已在此线程中打开。
- 如果是 - 什么都不做。
- 如果否 - 创建事务并将其绑定到当前线程(通过 ThreadLocal 变量)。
- 方法完成后 - 检查事务是否由我启动。
- 如果没有 - 什么也不做。
- 如果是 - 提交事务,关闭会话。
我假设你用 @Transactional
标记你的测试。这意味着要测试谁启动会话和事务。存储库只使用已经打开的。然后因为没有提交 - 没有刷新。然后你使用 MockMvc - 在同一个线程中工作。它通过 @Transactional
或 OSIV
也发现交易已经开始。所以事务被重用了。
然后进入您的核心逻辑 - 您正在执行一些 SELECT
语句,这会刷新当前会话中未决的 SQL 语句。所以你原来的 save()
刚刚被冲洗了。
现在,在您的逻辑末尾,您再次执行 save()
,它将 INSERT 语句放入待处理的 SQL 查询中。测试完成后,它只是回滚事务,最终的 INSERT
不会发生。除非.. 你正在做你提到的 SELECT
陈述。
所以最后 - 不要忘记在与 ORM 相关的测试中执行 flush()
和 clear()
。这些方法存在于 Hibernate 的 Session
或 JPA 的 EntityManager
中。前者可以用:SessionFactory#getCurrentSession()
完成,后者可以用:
@PersistenceContext
EntityManager entityManager;
PS:我没有看到任何标有@Transactional
的生产代码。如果不这样做,您可能 运行 会遇到问题。
PPS: this is not a unit test.