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

时间:2014-12-30 20:24:25

标签: spring spring-data spring-data-jpa spring-data-rest

我正在评估弹簧数据休息,并且我遇到的情况似乎不再对我有利。

说我有一系列物品。

父母 - 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%的问题。有没有我可以在这个操作的自定义处理程序中填充的地方,而不重写我免费获得的所有其他操作?

1 个答案:

答案 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的方法handleNoMatchorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping的父级),您可以看到以下内容:

else if (patternAndMethodMatches.isEmpty() && !allowedMethods.isEmpty()) {
    throw new HttpRequestMethodNotSupportedException(request.getMethod(), allowedMethods);
}
  • patternAndMethodMatches.isEmpty():如果 url 并且方法(TRUEGET,...)不匹配,则返回POST

因此,如果您要求/users/{id} GET它将是TRUE,因为GET仅存在于 Spring Data REST 公开的存储库控制器上。

  • !allowedMethods.isEmpty():如果至少有一个方法TRUEGET或其他内容匹配给定的网址,则返回POST

/users/{id} GET也是如此,因为/users/{id} DELETE存在。

所以 Spring 会抛出HttpRequestMethodNotSupportedException


为了旁路这个问题,我使用以下逻辑创建了自己的HandlerMapping

  • HandlerMapping有一个HandlerMapping列表(此处RequestMappingInfoHandlerMappingRepositoryRestHandlerMapping
  • 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à