Смешивание Spring MVC + Spring Результаты данных Rest в нечетных MVC-ответах

У меня есть два объекта JPA: один с экспортированным репозиторием SDR, а другой с контроллером Spring MVC и неэкспортированным репозиторием.

Открытый объект MVC имеет ссылку на управляемый объект SDR. См. Ниже ссылку на код.

Проблема возникает при извлечении User из UserController. Управляемый объект SDR не будет сериализоваться, и кажется, что Spring может пытаться использовать ссылки HATEOAS в ответе.

Здесь a GET для полностью заполненного User выглядит следующим образом:

{
  "username": "[email protected]",
  "enabled": true,
  "roles": [
    {
      "role": "ROLE_USER",
      "content": [],
      "links": [] // why the content and links?
    }
    // no places?
  ]
}

Как я могу вернуть объект User из моего контроллера с помощью встроенного управляемого объекта SDR?

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
}

Ответ 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. Он может быть другим, но схожим в зависимости от указанной конфигурации.

Spring будет делать это, когда Address управляется SDR. Если бы он не управлялся SDR вообще, он включал бы весь адресный объект в ответ. Я подозреваю, что один объясняет поведение, которое вы видите.

Роли

Вы не указали информацию о UserRole, но на основе кода, который, по-видимому, не управляется за пределами User и, следовательно, не имеет зарегистрированного репозитория Spring. Если это так, то почему он внедряется - нет другого репозитория для ссылки на.

content и links под ролями выглядит как Spring, пытаясь сериализовать его как a Page. Обычно content будет иметь массив ресурсов, а links будет иметь такие ссылки, как "я" или ссылки на другие ресурсы. Я не уверен, что это значит.

Место

У Place есть собственный Spring репозиторий данных, поэтому он будет рассматриваться как управляемый объект и связан с ним, а не с встроенным. Я подозреваю, что вы ищете проекцию. Оформить заказ Spring документация по проекциям. Он будет выглядеть примерно так:

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

Это должно сериализовать имя пользователя, включить и роли и опустить все остальное. Я лично не использовал прогнозы, поэтому я не могу ручаться за то, насколько хорошо он работает, но это решение в документации.

РЕДАКТИРОВАТЬ: Пока мы говорим об этом, обратите внимание, что это относится также к созданию или обновлению ресурсов. Spring ожидает, что ресурс будет URL. Поэтому, взяв пример Person/Address, если бы я создал нового человека, мое тело могло бы выглядеть так:

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

Это довольно легко забыть об этом, так как обширное, обширное, огромное, огромное, подавляющее большинство API-интерфейсов REST не являются REST, а SDR/HATEOAS придерживаются самоуверенного представления REST (например, что он должен быть REST, для одного).

Ответ 2

Вы можете очень хорошо использовать @ResponseEntity в своем контроллере, а затем установить User Object в ResponseEntity.

См. пример ниже:

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

Затем на стороне клиента вы можете позвонить, restTemplate.getForEntity

Подробнее см. ниже:

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...-