REST: Расширение CrudRepository для объектов, ссылающихся на себя, создает исключение

#spring #spring-boot #spring-data-jpa

#spring #spring-boot #spring-data-jpa

Вопрос:

У меня есть одна проблема, с которой мне могли бы помочь более опытные пользователи.

У меня есть два класса: Company и Unit:

 @Entity(name="Company")
public class Company
{
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  private String name;
}

@Entity(name="Unit")
public class Unit
{

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

  private String name;

  @JsonIgnore
  @ManyToOne(optional = false)
  private Company company;

  @JsonIgnore
  @OneToMany(mappedBy = "parent")
  private List<Unit> subunits = new ArrayList<Unit>();

  @JsonIgnore
  @ManyToOne
  private Unit parent;

}
  

Как вы можете видеть, у меня есть пара отношений ManyToOne, и объект Unit ссылается на себя.

Я также добавил несколько аннотаций @JsonIgnore для полей, которые я не хочу выводить по запросу REST API. Например, при запросе единиц измерения я не хочу, чтобы возвращался весь объект Company и все подразделения. Тем не менее, я хотел бы вернуть ParentID для единицы. Кроме того, при добавлении нового модуля через REST API я хочу, чтобы клиент мог установить родительский модуль Unit, установив «ParentID»:<id>.

Итак, я решил, что правильный способ — добавить еще одно поле «ParentID» в Unit class, которое помечено @Transient. Таким образом, оно не сохраняется в базе данных, но будет выводиться при запросе.

 @Transient
private long parentId;
  

При возврате объекта Unit достаточно написать средство получения, подобное этому:

 public long getParentId()
{
  if (parent != null)
  {
    this.parentId = parent.getId();
  }
  return this.parentId;
}
  

и при сохранении нового блока:

 Unit parent = unitService.getUnit(newUnit.getParentId()); // returns null if there's no Unit with supplied ID
newUnit.setParent(parent);
unitRepository.save(newUnit);
  

Все, что осталось, это расширить CrudRepository с помощью:

 Iterable<Unit> findByParentId(long parentId);
  

когда я хочу получить все подразделения Unit (http://localhost:8080/company/1/units/2/subunits ).

Однако строка выше выдает:

 Error creating bean with name 'unitRepository': FactoryBean threw exception on object creation;
nested exception is java.lang.IllegalArgumentException: Failed to create query for method public
abstract java.lang.Iterable c.a.f.m.unit.UnitRepository.findByParentId(long)! Unable to locate 
Attribute  with the the given name [parentId] on this ManagedType [c.a.f.m.unit.Unit]
  

Похоже, что для объектов, ссылающихся на себя, Spring не может различать родительский идентификатор переходного поля и непереходящий parent.getId() при создании метода CrudRepository findByParentId (длинный идентификатор).

Я знаю, что это можно легко исправить, переименовав поле transient, но я хотел знать, есть ли более элегантный способ решить эту проблему? Может быть, кто-то более опытный придумал какой-то другой способ справиться с подобными ситуациями? У меня такое чувство, что мне следовало бы делать эти вещи немного по-другому…

Спасибо!

Ответ №1:

Хорошо, я должен был ознакомиться с документацией, прежде чем публиковать вопрос.

Здесь описано, как Spring переводит имена методов в реальные запросы. Все, что мне нужно сделать, это добавить символ подчеркивания, чтобы указать Spring, где разделить свойства. Итак, мой метод findByParentId должен быть назван findByParent_Id. Таким образом, Spring проигнорирует свойство ParentID и перейдет к parent.id собственность.

С уважением!