REST: Extending CrudRepository for self-referencing entities throws an exception

Viewed 95

I have one issue that more experienced users might help me with.

I have two classes, Company and 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;

}

As you can see, I have a couple of ManyToOne relationships and Unit Entity is self-referencing.

I also added few @JsonIgnore annotations for fields which I don't want to be output on REST API request. For example, when querying for Units, I don't want whole Company object and all sub-units to be returned. However, I would like to return parentId for a Unit. Also, when adding new Unit through REST API, I want a client to be able to set Unit's parent Unit by setting "parentId":<id>.

So I figured that the correct way is to add another field "parentId" in Unit class which is annotated with @Transient. This way, it doesn't get saved to the database, but it will be output when queried.

@Transient
private long parentId;

When returning a Unit object it is sufficient to write a getter like this:

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

and when persisting a new Unit:

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

All that's left is to extend CrudRepository with:

Iterable<Unit> findByParentId(long parentId);

when I want to get all Unit's sub-units (http://localhost:8080/company/1/units/2/subunits).

However, the line above throws:

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]

It seems that for self-referencing entities Spring is unable to differentiate between transient field parentId and non-transient parent.getId() when creating CrudRepository's method findByParentId(long id).

I know this can be easily fixed by renaming transient field, but I wanted to know if there's more elegant way to solve this? Maybe someone more experienced figured some other way of dealing with situations like this? I have a feeling that I should me doing these things a bit differently...

Thanks!

1 Answers

OK, I should have consulted the documentation before posting a question.

Here's description how Spring translates from method names to actual queries. All I need to do is add an underscore to tell Spring where to split properties. So, my method findByParentId should be named findByParent_Id. That way, Spring will ignore parentId property and go for parent.id property.

Regards!

Related