混合弹簧 MVC + 弹簧数据静止会导致奇怪的 MVC 响应

我有两个JPA实体,一个带有SDR导出的存储库,另一个带有Spring MVC控制器,以及一个未导出的存储库。

MVC 公开的实体具有对 SDR 托管实体的引用。有关代码参考,请参阅下文。

从 中检索 时,问题开始发挥作用。SDR 托管实体不会序列化,并且似乎 Spring 可能正在尝试在响应中使用 HATEOAS 引用。UserUserController

以下是完全填充的 a 的外观:GETUser

{
  "username": "foo@gmail.com",
  "enabled": true,
  "roles": [
    {
      "role": "ROLE_USER",
      "content": [],
      "links": [] // why the content and links?
    }
    // no places?
  ]
}

如何从具有嵌入式 SDR 托管实体的控制器中明确返回实体?User

春季 MVC 管理

实体

@Entity
@Table(name = "users")
public class User implements Serializable {

    // UID

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonIgnore
    private Long id;

    @Column(unique = true)
    @NotNull
    private String username;

    @Column(name = "password_hash")
    @JsonIgnore
    @NotNull
    private String passwordHash;

    @NotNull
    private Boolean enabled;

    // No Repository
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    @NotEmpty
    private Set<UserRole> roles = new HashSet<>();

    // The SDR Managed Entity
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "user_place", 
        joinColumns = { @JoinColumn(name = "users_id") }, 
        inverseJoinColumns = { @JoinColumn(name = "place_id")})
    private Set<Place> places = new HashSet<>();

    // getters and setters
}

回购

@RepositoryRestResource(exported = false)
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
    // Query Methods
}

控制器

@RestController
public class UserController {

    // backed by UserRepository
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping(path = "/users/{username}", method = RequestMethod.GET)
    public User getUser(@PathVariable String username) {
        return userService.getByUsername(username);
    }

    @RequestMapping(path = "/users", method = RequestMethod.POST)
    public User createUser(@Valid @RequestBody UserCreateView user) {
        return userService.create(user);
    }

    // Other MVC Methods
}

SDR 托管

实体

@Entity
public class Place implements Serializable {

    // UID

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @NotBlank
    private String name;

    @Column(unique = true)
    private String handle;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "address_id")
    private Address address;

    @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "contact_info_id")
    private ContactInfo contactInfo;

    // getters and setters
}

回购

public interface PlaceRepository extends PagingAndSortingRepository<Place, Long> {
    // Query Methods
}

答案 1

简而言之:Spring Data REST和Spring HATEOAS劫持了ObjectMapper,并希望将资源之间的关系表示为链接,而不是嵌入资源。

将具有一对一关系的实体与另一个实体:

@Entity
public class Person {
    private String firstName;
    private String lastName;
    @OneToOne private Address address;
}

SDR/HATEOAS 将以链接形式返回地址:

{
    "firstName": "Joe",
    "lastName": "Smith",
    "_links": {
        "self": { "href": "http://localhost:8080/persons/123123123" },
        "address": { "href": "http://localhost:8080/addresses/9127391273" }
    }
}

默认格式可能会根据类路径上的内容而更改。我相信这是我的例子中的HAL,当你包含SDR和HATEOAS时,这是默认值。它可能不同但相似,具体取决于所述配置

当SDR管理时,Spring将做到这一点。如果它根本不由SDR管理,它将在响应中包含整个地址对象。我怀疑仅凭这一点就可以解释你所看到的行为。Address

角色

您尚未包含信息,但根据您的代码,它似乎可能未在外部进行管理,因此没有注册Spring Data存储库。如果是这种情况,那就是它被嵌入的原因 - 没有其他存储库可以“链接”到。UserRoleUser

和下面的角色看起来像Spring试图像.通常将具有一系列资源,并将具有诸如“self”之类的链接或指向其他资源的链接。我不确定是什么原因造成的。contentlinksPagecontentlinks

地方

Place有自己的Spring Data存储库,因此它将被视为一个托管实体,并链接到而不是嵌入。我怀疑你要找的是一个投影。查看有关投影的春季文档。它看起来像这样:

@Projection(name = "embedPlaces", types = { User.class })
interface EmbedPlaces {
    String getUsername();
    boolean isEnabled();
    Set<Place> getPlaces();
}

这应该序列化用户名,启用和角色,并省略其他所有内容。我还没有亲自使用过投影,所以我不能保证它的工作效果如何,但这是文档中的解决方案。

编辑:当我们在这里时,请注意,这也适用于创建或更新资源。Spring将期望资源作为URL。因此,以“人/地址”为例,如果我要创建一个新人,我的身体可能看起来像这样:

{
    "firstName": "New",
    "lastName": "Person",
    "address": "http://localhost:8080/addresses/1290312039123"
}

人们很容易忘记这些事情,因为绝大多数的“REST”API都不是REST,SDR/HATEOAS对REST持固执己见(例如,它应该是REST)。


答案 2

您可以在控制器中使用@ResponseEntity,然后在响应实体中设置用户对象。

请参阅下面的示例:

ResponseEntity<User> respEntity = new ResponseEntity<User>(user, HttpStatus.OK);

然后在客户端,您可以调用,restTemplate.getForEntity

请参阅下面的 restTemplate 文档:

http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html#getForObject-java.lang.String-java.lang.Class-java.lang.Object...-


推荐