混合 Spring MVC + Spring Data Rest 会导致奇怪的 MVC 响应
Posted
技术标签:
【中文标题】混合 Spring MVC + Spring Data Rest 会导致奇怪的 MVC 响应【英文标题】:Mixing Spring MVC + Spring Data Rest results in odd MVC responses 【发布时间】:2016-09-10 23:18:51 【问题描述】:我有两个 JPA 实体,一个带有 SDR 导出的存储库,另一个带有 Spring MVC 控制器和一个未导出的存储库。
MVC 公开实体具有对 SDR 托管实体的引用。请参阅下面的代码参考。
从UserController
检索User
时,问题就出现了。 SDR 托管实体不会序列化,似乎 Spring 可能会尝试在响应中使用 HATEOAS refs。
下面是 GET
与完全填充的 User
的样子:
"username": "foo@gmail.com",
"enabled": true,
"roles": [
"role": "ROLE_USER",
"content": [],
"links": [] // why the content and links?
// no places?
]
如何使用嵌入式 SDR 托管实体从我的控制器中明确返回 User
实体?
Spring 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
【问题讨论】:
我假设您确实有 PlaceRepository 的 @Repository 注释 - 只是没有在此处发布?您能否也添加异常的文本? @lenach87 - SDR 不需要@Repository
注释,除非您需要进一步配置它。也没有Exception,只是缺少序列化。
您的 JPA 实现可能有问题?如果您使用的是 Hibernate,它可以只加载一个包。您可以创建一个自定义查询来强制它立即加载,或者在将其作为响应发送之前简单地调用属性的访问器(这将强制休眠加载属性包)。
也许检查一下,假设您使用杰克逊进行序列化:***.com/questions/19580856/…
您可以添加您的存储库配置吗?是否有可能有两个 entityManager 在处理这些存储库,而一个看不到另一个中的实体?
【参考方案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 时,这是默认设置。 根据上述配置,它可能不同但相似。
当Address
由 SDR 管理时,Spring 将执行此操作。如果它根本不受 SDR 管理,它将在响应中包含整个地址对象。我怀疑仅此一项就可以解释您所看到的行为。
角色
您尚未包含有关 UserRole
的信息,但根据您的代码,它似乎未在 User
之外进行管理,因此未注册 Spring Data 存储库。如果是这种情况,这就是它被嵌入的原因——没有其他存储库可以“链接”到。
角色下的content
和links
看起来像Spring 试图将其序列化为Page
。通常content
将有一个资源数组,links
将有诸如“自我”之类的链接或指向其他资源的链接。我不确定是什么原因造成的。
地点
Place 拥有自己的 Spring Data 存储库,因此它将被视为托管实体并链接到而不是嵌入。我怀疑你正在寻找的是一个投影。结帐春天documentation on projections。它看起来像这样:
@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 中设置用户对象。
请看下面的例子:
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...-
【讨论】:
我不太确定这对我有什么帮助。以上是关于混合 Spring MVC + Spring Data Rest 会导致奇怪的 MVC 响应的主要内容,如果未能解决你的问题,请参考以下文章
混合前端和服务器端技术(Spring、Thymeleaf、AngularJS)
Spring MVC学习笔记---Spring MVC 的HelloWorld