我正在评估弹簧数据休息,并且我遇到的情况似乎不再对我有利。
说我有一系列物品。
父母 - 1:M - 儿童
Parent
Long id
String foo
String bar
@OneToMany(...)
@JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false)
Collection<Child> items
setItems(items) {
this.items.clear();
this.items.addAll(items);
}
@Table(name = "items", uniqueConstraints = {@UniqueConstraint(columnNames = {"parent_id", "ordinal"})})
Child
Long id
String foo
Integer ordinal
数据库有一个约束条件,即同一父级的子级不能在一个特定字段中具有冲突值,“ordinal”。
我想要PATCH到父实体,覆盖子集合。问题来自于hibernate的默认行为。 Hibernate不会清除从清除集合到添加新项目之后的更改。这违反了约束,即使最终状态不会。
Cannot insert duplicate key row in object 'schema.parent_items' with unique index 'ix_parent_items_id_ordinal'
我已经尝试使用@UniqueConstraints()将此约束映射到子实体,但这似乎不会改变行为。
我目前正在解决这个问题,方法是手动查看当前项目,并使用新值更新会导致约束违规的项目。
我错过了什么吗?这似乎是一个相当常见的用例,但也许我正在努力将hornnate鞋拔成遗留数据库设计。我希望能够在不必修改架构的情况下使我们的当前数据更有效。
我看到我可以编写一个自定义控制器和服务,àlahttps://github.com/olivergierke/spring-restbucks,这将让我处理entityManager并在它们之间刷新。我认为这样的问题是,我似乎首先失去了使用spring-data-rest的全部好处,它几乎没有代码解决了99%的问题。有没有我可以在这个操作的自定义处理程序中填充的地方,而不重写我免费获得的所有其他操作?
答案 0 :(得分:2)
为了自定义 Spring Data REST (我的方式,我不得不谈论 Spring Data REST 的人),如下所示:
考虑我们在UserRepository
上有一个公开的存储库 /users/
,您至少应该拥有以下API:
...
/users/{id} GET
/users/{id} DELETE
...
现在您要覆盖/users/{id} DELETE
,但要通过 Spring Data REST 来处理其他API。
自然方法(在我看来)是编写自己的UserController
(以及您的自定义UserService
),如下所示:
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@RequestMapping(method = RequestMethod.DELETE, value = "/{user}")
public void delete(@Valid @PathVariable("user") User user) {
if (!user.isActive()) {
throw new UserNotFoundException(user);
}
user.setActive(false);
userService.save(user);
}
}
但是,通过这样做,/users
现在将处理以下映射org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
,而不是org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping
。
如果您关注org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping
的方法handleNoMatch(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
的父级),您可以看到以下内容:
else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
patternAndMethodMatches.isEmpty()
:如果 url 并且方法(TRUE
,GET
,...)不匹配,则返回POST
。因此,如果您要求/users/{id} GET
它将是TRUE
,因为GET
仅存在于 Spring Data REST 公开的存储库控制器上。
!allowedMethods.isEmpty()
:如果至少有一个方法TRUE
,GET
或其他内容匹配给定的网址,则返回POST
。 /users/{id} GET
也是如此,因为/users/{id} DELETE
存在。
所以 Spring 会抛出HttpRequestMethodNotSupportedException
。
为了旁路这个问题,我使用以下逻辑创建了自己的HandlerMapping
:
HandlerMapping
有一个HandlerMapping
列表(此处RequestMappingInfoHandlerMapping
和RepositoryRestHandlerMapping
)HandlerMapping
遍历此列表并委派请求。如果发生异常,我们保留它(我们实际上只保留第一个异常)并继续执行另一个处理程序。最后,如果列表中的所有处理程序都抛出异常,我们将重新抛出第一个异常(之前保持不变)。此外,我们实现org.springframework.core.Ordered
以便将处理程序放在org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
之前。
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @author Thibaud Lepretre
*/
public class OrderedOverridingHandlerMapping implements HandlerMapping, Ordered {
private List<HandlerMapping> handlers;
public OrderedOverridingHandlerMapping(List<HandlerMapping> handlers) {
Assert.notNull(handlers);
this.handlers = handlers;
}
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Exception firstException = null;
for (HandlerMapping handler : handlers) {
try {
return handler.getHandler(request);
} catch (Exception e) {
if (firstException == null) {
firstException = e;
}
}
}
if (firstException != null) {
throw firstException;
}
return null;
}
@Override
public int getOrder() {
return -1;
}
}
现在让我们创建我们的bean
@Inject
@Bean
@ConditionalOnWebApplication
public HandlerMapping orderedOverridingHandlerMapping(HandlerMapping requestMappingHandlerMapping,
HandlerMapping repositoryExporterHandlerMapping) {
List<HandlerMapping> handlers = Arrays.asList(requestMappingHandlerMapping, repositoryExporterHandlerMapping);
return new OrderedOverridingHandlerMapping(handlers);
}
Etvoilà。