Table of Contents

Hibernate is the implementation of the Java Persistence API.

Annotations

Tutorials: http://viralpatel.net/blogs/hibernate-one-to-many-annotation-tutorial/

In examples there is:

ACHTUNG:

@ManyToOne

Then the modelling will look as following:

@Entity
@Table(name="EMPLOYEE")
public class Employee {
  
    @ManyToOne
    @JoinColumn(name="department_id")
    private Department department;
}
@Entity
@Table(name="DEPARTMENT")
public class Department{
  
    @OneToMany(mappedBy="department")
    private Set<Employee> employees;
}

Discriminators

Discriminators are used for for storing class hierarchies in a single table. If having RedCar.class inheriting from Car.class - both stored in one table, then discriminator tells - hwne t orestore a RedCar and when t orestore a Car.

The element is required for polymorphic persistence using the table-per-class-hierarchy mapping strategy and declares a discriminator column of the table. The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row.

Details: http://docs.jboss.org/hibernate/core/3.5/reference/en-US/html/mapping.html#mapping-declaration-discriminator

Spring

ManyToOne

The Domain Model looks as following

Campus -is-> Location
CampusPart -is-> Location

Campus -hasmany-> CampusPart

Introduce a Superclass with an ID.


@MappedSuperclass
@Table(name = "locations", schema = "public")
public abstract class ELocation {

    @Id
    @Column(name = "ID", unique = true, nullable = false, insertable = false, updatable = false)
    protected UUID id = UuidCreator.getSequential();
}

Bidirectional Many-To-Many

Here Both Entities Campus and CampuPart have a Link on each other. This is called a BiDirectional modeling

Owning - with Join Table

Introduce an ECampusPart Here the JoinTable is on CampusPart, so it is the owning-side of the connection.


@Entity(name = "ECampusPart")
@Table(name = "campusparts", schema = "public")
public class ECampusPart extends ELocation  {


  /*
    Modeling a bidirectional relationship here.

    JoinTable means - that relationships will be extracted explicitely to an own table.
    Allows to explicitely name the columns: campuspart_id and campus_id here

   */
  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
  @JoinTable(
          name = "campus_campuspart",
          joinColumns = {@JoinColumn(name = "campuspart_id")},
          inverseJoinColumns = {@JoinColumn(name = "campus_id")}
  )
  private ECampus campus;

Using “JoinTable” results in the creation of a special table


@Entity(name = "ECampusPart")
@Table(name = "campusparts", schema = "public")
public class ECampusPart extends ELocation  {

  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
  @JoinTable(
          name = "campus_campuspart",
          joinColumns = {@JoinColumn(name = "campuspart_id")},
          inverseJoinColumns = {@JoinColumn(name = "campus_id")}
  )
  private ECampus campus;

Results in

campuses

dtypeidobject_typename
ECampusPart497ffe9a-51bb-4ab4-bc3d-b8923d620703CampusPartViennaSmart City Campus front part

campusparts

dtypeidobject_typename
ECampusdbadab87-7b6d-4e8b-b2c8-0663d68dfe7bCampusViennaSmart City Campus

campus_campuspart

campus_idcampuspart_id
497ffe9a-51bb-4ab4-bc3d-b8923d620703dbadab87-7b6d-4e8b-b2c8-0663d68dfe7b
Owning-side - with embedded foreign key

Omitting the “JoinTable” results in embedding the Foreign key campus_id - into the CampusPart table


@Entity(name = "ECampusPart")
@Table(name = "campusparts", schema = "public")
public class ECampusPart extends ELocation  {

  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
  private ECampus campus;

Results in

campuses

dtypeidobject_typenamecampus_id
ECampusPart497ffe9a-51bb-4ab4-bc3d-b8923d620703CampusPartViennaSmart City Campus front partdbadab87-7b6d-4e8b-b2c8-0663d68dfe7b

campusparts

dtypeidobject_typename
ECampusdbadab87-7b6d-4e8b-b2c8-0663d68dfe7bCampusViennaSmart City Campus
Inverse-side

Introduce an ECampus. Here the mappedBy is on Campus, so it is the inverse-side of the connection.


@Entity(name="ECampus")
@Table(name = "campuses", schema = "public")
public class ECampus extends ELocation  {

  @OneToMany(mappedBy = "campus", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
  private Set<ECampusPart> campusParts = new HashSet<>();
  
}

Warning: bidirectional relationships

ManyToMany relationships can lead to inconsitancies when

Transactional

Seems like when wiring entities, which are affected on both sides - it should happen in 1 transaction.

In Spring boot you need to annotate a method with @Transactional

At a high level, Spring creates proxies for all the classes annotated with @Transactional, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction.

https://www.baeldung.com/transaction-configuration-with-jpa-and-spring

https://thorben-janssen.com/transactions-spring-data-jpa/

@Service
@Getter
public class EquipmentService {

    @Autowired
    private EquipmentRepository equipmentRepository;
     
    @Transactional
    public void updateEquipmentFeeds(ResourcePK equipmentFeeds, ResourcePK equipmentTarget) {
        Assert.isTrue(equipmentFeeds.getPartitionId().equals(equipmentTarget.getPartitionId()), "Only allow feeds in same partition");
        Equipment e1 = equipmentRepository.findById(equipmentFeeds).get();
        Equipment e2 = equipmentRepository.findById(equipmentTarget).get();
        e1.getFeeds().add(e2);
        equipmentRepository.save(e1);
    } 
}

ManyToMany with composite key to self


    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name="FeedsRel",
            joinColumns={
                    @JoinColumn(
                            name = "feedsrel_child_id",
                            referencedColumnName = "id"),
                    @JoinColumn(
                            name = "feedsrel_child_partitionId",
                            referencedColumnName = "partitionId")
            })
    private Set<Equipment> feeds = new HashSet<Equipment>();


    // Associations marked as mappedBy must not define database mappings like @JoinTable or @JoinColumn
    @ManyToMany( cascade = CascadeType.ALL, mappedBy="feeds", fetch = FetchType.EAGER)
    private Set<Equipment> fedBy = new HashSet<Equipment>();