sonoerin
sonoerin

Reputation: 5185

Nested Hibernate Entity

I am creating a Spring JPA (PostgresSQL & H2) service that deals with offices. I can have a stand-alone office, or an parent office (corporate) with children (remote) offices. Offices have an address and a list of employees (who can belong to more than one office). I am struggling with the design and would really appreciate any help you can offer.

My tables are currently in H2 for testing/dev so I don't have any referential integrity on the tables.
This is making me wonder if my model couldn't be improved. Is there a better way to do this sort of recursive/nested relationship?

UPDATE Based on the excellent feedback from jfneis, I now have this approach:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "OfficeType")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "officeId")
public abstract class Office {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "officeId", updatable = false, nullable = false)
    private long officeId;

    @Column
    private String name;
    @Column
    private String ein;
    @Column
    @Temporal(TemporalType.DATE)
    private Date startDate;
    @Column
    @Temporal(TemporalType.DATE)
    private Date endDate;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "addressId", nullable = false)
    private Address address;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @Fetch(value = FetchMode.SUBSELECT)
    private List<Employee> employees = new ArrayList<>();
}

and the corporate office:

@Entity(name = "CorporateOffice")
@DiscriminatorValue("Corporate")
public class CorporateOffice extends Office {

    @Column
    private String url;
}

and remote office:

@Entity(name = "RemoteOffice")
@DiscriminatorValue("Remote")
public class RemoteOffice extends Office {

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "corporateOfficeId")
    private Office corporateOffice;
}

If I understand correctly, both office types will be stored in the "office" table with a column indicating which type it is.

On startup I am seeding some test data so I can further see what is going on:

@Component
@Transactional
public class SeedOffices {

@Autowired
private OfficeRepository officeRepository;

@PostConstruct
public void seedOffices() {
    CorporateOffice corporate = new CorporateOffice();
    corporate.setName("World Domination Corp");
    corporate.setUrl("www.corporationsownstheworld.com");
    corporate.setStartDate(new Date());
    Address corpAddr = new Address();
    corpAddr.setAddressLine1("corp line 1");
    corpAddr.setCity("Denver");
    corporate.setEin("123456789");
    corporate.setAddress(corpAddr);
    officeRepository.save(corporate);

    RemoteOffice office1 = new RemoteOffice();
    office1.setStartDate(new Date());
    Address remote1Addr = new Address();
    remote1Addr.setAddressLine1("remote 1 line 1");
    remote1Addr.setCity("Cheyanne");
    office1.setAddress(remote1Addr);
    officeRepository.save(office1);
    office1.setCorporateOffice(corporate);

    RemoteOffice office2 = new RemoteOffice();
    Address remote2Addr = new Address();
    remote2Addr.setAddressLine1("remote 2 line 1");
    remote2Addr.setCity("Calumet");
    office2.setAddress(remote2Addr);
    officeRepository.save(office2);
    office2.setCorporateOffice(corporate);
}

}

Upvotes: 1

Views: 2596

Answers (1)

jfneis
jfneis

Reputation: 2197

This is a design question with subjective answers (which is not the kind of answer SO is supposed to answer).

Anyway: I don't like the single Office class. You have only one class for 2 different behaviors: a Corporate office (which CAN have remote offices) and a Remote office (that CAN have a parent office).

Keeping references in both points is up to your business requirements, but my suggestion is: have an abstract class Office, with common attributes, and 2 children classes: CorporateOffice and RemoteOffice, each of them dealing with the desired attributes.

You can map both in the same table using Single Table strategy. For a very good article about JPA subclassing strategies, take a look at this article.


Edit: answering your 2nd question about RemoteOffice mapping:

I believe your RemoteOffice mapping is wrong. You declared corporateOffice as a @OneToMany, but it's actually a @ManyToOne relationship, isn't it? Please test @ManyToOne + @JoinColumn in this field to check if solves your problem.

Upvotes: 1

Related Questions