1

I'm struggling with inserting @OneToMany entities in the JPA-Hibernate setup.

There are two associated tables with one of the table having the foreign key as the primary key of the source table.

employee

- id (PK)

employee_location

- employee_id (FK to employee)

Here are my entities:

Employee

@Entity(name = "employee")
class Employee {
     
     @Id
     @Column(name = "id")
     @GeneratedValue()
     private Long id;

     @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
     @JoinColumn(name = "employee_id", referencedColumnName = "id")
     private List<EmployeeLocation> employeeLocations;

} 

Employee Location

@Entity(name = "employee_location")
class EmployeeLocation {
     
         @Id
         @Column(name = "employee_id")
         private Long employeeId;

         @ManyToOne(cascade = CascadeType.ALL)
         @JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
         private Employee employee;

} 

Saving the entities:

List<EmployeeLocation> locations = Arrays.asList(new EmployeeLocation(), new EmployeeLocation()); 

Employee employee = new Employee();
employee.setLocations(locations);

employee.save(); // Throws exceptions

Which throws me this error: org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():

I tried changing @Entity to @Embeddable and removed the @Id on EmployeeLocation, but it gave me other Unmapped entity exceptions.

How do I handle inserting/updating @OneToMany entities? Is this possible?

pickoneusername
  • 67
  • 2
  • 10

3 Answers3

1

In the case of Employee entity having a OneToOne relationship with EmployeeLocation entity you can use just @MapsId. This way, the EmployeeLocation id property is populated with the identifier of the post association.

class EmployeeLocation {
     
    @Id
    @Column(name = "id")
    private Long id;

    @OneToOne
    @MapsId
    private Employee employee;
} 

but since your Employee entity has an OneToMany relationship with EmployeeLocation, Employee id property value can't be used as EmployeeLocation id property value because two or more EmployeeLocation entities asociated to the same Employee entity will have the same id value.

You'll need something like this:

@Entity
public class EmployeeLocation {    

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    ...
    @ManyToOne Employee employee;
}

In both cases you need to bind the EmployeeLocation entity to Employee entity, for example:

class Employee {
    ....
    public void addLocation(EmployeeLocation location) {
        location.setEmployee(this);
        this.employeeLocations.add(location);
    }

    public void setLocations(List<EmployeeLocation> locations) {
        for (EmployeeLocation location : locations) {
            location.setEmployee(this);         
        }
        this.employeeLocations = locations;
    }
    ....
}

ANOTHER OPTION: Using ElementCollection


@Entity(name = "employee")
class Employee {
   
    @Id
    @Column(name = "id")
    @GeneratedValue
    private Long id;

    @ElementCollection
    @CollectionTable(
        name="employee_location",
        joinColumns=@JoinColumn(name="EMPLOYEE_ID"))    
    private Set<EmployeeLocation> employeeLocations;
}

@Embeddable
class EmployeeLocation {

    // properties
}


  • I see you added a new column `id` to the `employee_location` table. But I don't want to change `employee_location` table, since it already has a Primary Key = employee_id (Foreign Key) – pickoneusername Nov 09 '21 at 08:31
0

How do I handle inserting/updating @OneToMany entities? Is this possible?

If you want the DB to generate the primary key values for you, you need to ask for it by using the @GeneratedValue annotation

@Entity(name = "employee")
class Employee {
     
     @Id
     @Column(name = "id")
     @GeneratedValue // mean -> "Hey, DB, give me an ID"!
     private Long id;

Same applies for EmployeeLocation

More details can be found here

If this does not fully solve your problem, leave a comment.

Arthur Klezovich
  • 2,595
  • 1
  • 13
  • 17
  • Yes, that's right. I forgot to add the `@GeneratedValue` annotation on `Employee`. Which I did now. But for `EmployeeLocation`, the `employee_id` column is the FK. It is not a generated value, but it depends on the value generated by `Employee` – pickoneusername Nov 08 '21 at 14:51
0

In your EmployeeLocation entity (detail) you cannot have as primary key the master's primary key, it needs its own. As follows:

@Entity(name = "employee_location")
class EmployeeLocation {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "employee_location_id")
  private Integer id;

  @ManyToOne(cascade = CascadeType.ALL)
  @JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
  private Employee employee;

} 
galaxy
  • 159
  • 1
  • 7
  • Could you please tell me why this is not possible in JPA? Or point to me to some documentation? – pickoneusername Nov 09 '21 at 08:32
  • Sure. Because every table should have its own primary key. Check this [https://stackoverflow.com/a/840182/2933780]. – galaxy Nov 09 '21 at 13:48
  • Yes, the table does have a primary key. The primary key of the table `employee_location` is `employee_id`, which is a foreign key too. It's both. I now edited my question to explicitly mention this point. – pickoneusername Nov 10 '21 at 13:37
  • You cannot have this scenario because primary key is a unique key and the purpose of a foreing key is you can have multiple references to the master (parent) table in your detail (child) table. – galaxy Nov 10 '21 at 13:45
  • I see your point and you're correct. Since it;s a one-to-many, it doesn't make sense. Hence I need a PK. – pickoneusername Nov 10 '21 at 13:54
  • Because it's a one-to-many relationship you need multiple references and you cannot have a primary key that does repeat. – galaxy Nov 10 '21 at 14:08