具有唯一约束的Spring数据Rest和集合

具有唯一约束的Spring数据Rest和集合,spring,spring-data,spring-data-jpa,spring-data-rest,Spring,Spring Data,Spring Data Jpa,Spring Data Rest,我正在评估SpringDataREST,遇到了这样一种情况:魔术似乎不再对我有利 假设我有一组物品 父-1:M-子 Parent Long id String foo String bar @OneToMany(...) @JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false) Collection<Child> items setItem

我正在评估SpringDataREST,遇到了这样一种情况:魔术似乎不再对我有利

假设我有一组物品

父-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
我尝试使用@UniqueConstraints()将此约束映射到子实体,但这似乎没有改变行为

我目前正在通过手动查看当前项目并用新值更新可能导致违反约束的项目来解决此问题

我错过什么了吗?这似乎是一个相当常见的用例,但可能我太努力了,无法将horn休眠到遗留数据库设计中。我希望能够在不必修改模式的情况下,根据当前数据进行操作

我发现我可以编写一个定制的控制器和服务,这可以让我处理entityManager,并在两者之间进行刷新。我认为这样做的问题是,似乎我首先失去了使用SpringDataREST的全部好处,它几乎不用代码就解决了99%的问题。是否有什么地方可以为这个操作填充自定义处理程序,而不必重写我免费获得的所有其他操作?

为了自定义Spring数据REST(我的方式,我必须与Spring数据REST的家伙讨论),如下所示:

@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/
上有一个公开的存储库
UserRepository
,您至少应该有以下API:

...
/users/{id} GET
/users/{id} DELETE
...
现在您希望覆盖
/users/{id}DELETE
,但保留其他API由Spring数据REST处理

自然的方法(同样在我看来)是编写您自己的
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

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;
    }
}
如果您注意
org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping
(org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping的父级)的方法,您可以看到以下内容:

else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
    throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
  • pattern和methodmatches.isEmpty()
    :如果url和方法(
    GET
    POST
    ,…)不匹配,则返回
    TRUE
因此,如果您要求
/users/{id}GET
,它将是
TRUE
,因为
GET
仅存在于Spring Data REST存储库控制器上

  • !allowedMethods.isEmpty()
    :如果至少有一个方法
    GET
    POST
    或其他内容与给定url匹配,则返回
    TRUE
对于
/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;
    }
}
import org.springframework.core.Ordered;
导入org.springframework.util.Assert;
导入org.springframework.web.servlet.HandlerExecutionChain;
导入org.springframework.web.servlet.HandlerMapping;
导入javax.servlet.http.HttpServletRequest;
导入java.util.List;
/**
*@作者Thibaud Lepretre
*/
公共类OrderedOverridingHandlerMapping实现HandlerMapping,Ordered{
私有列表处理程序;
public OrderedOverridingHandlerMapping(列表处理程序){
Assert.notNull(处理程序);
this.handlers=handlers;
}
@凌驾
public HandlerExecutionChain getHandler(HttpServletRequest请求)引发异常{
Exception firstException=null;
for(HandlerMapping处理程序:处理程序){
试一试{
return handler.getHandler(请求);
}捕获(例外e){
if(firstException==null){
firstException=e;
}
}
}
if(firstException!=null){
抛出第一个异常;
}
返回null;
}
@凌驾
public int getOrder(){
返回-1;
}
}
现在让我们创建我们的bean

@Inject
@Bean
@ConditionalOnWebApplication
public HandlerMapping orderedOverridingHandlerMapping(HandlerMapping requestMappingHandlerMapping,
                                                      HandlerMapping repositoryExporterHandlerMapping) {
    List<HandlerMapping> handlers = Arrays.asList(requestMappingHandlerMapping, repositoryExporterHandlerMapping);
    return new OrderedOverridingHandlerMapping(handlers);
}
@Inject
@豆子
@条件性网络应用
公共HandlerMapping orderedOverridingHandlerMapping(HandlerMappingRequestMappingHandlerMapping,
HandlerMapping存储库导出器HandlerMapping){
列表处理程序=Arrays.asList(requestMappingHandlerMapping、repositoryExporterHandlerMapping);
返回新的OrderedOverridingHandlerMapping(处理程序);
}

Et voilá。

你说的“我首先失去了使用spring data rest的全部好处”是什么意思?@Kakawait spring data rest似乎做了大量工作,通过rest控制器无缝地公开存储库,该控制器处理所有操作,包括POST、PUT、DELETE、PATCH、GET和使用HATEOAS链接注释实体。并不是说自己不可能编写控制器和服务层,而是我希望能够