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?
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.
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;
}
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
}
Related
#Entity
#Data
#NoArgsConstructor
public class Offer {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
#Data
#EqualsAndHashCode
#Entity
#NoArgsConstructor
public class User {
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch=FetchType.LAZY)
private Set<Offer> offers = new HashSet<Offer>();
}
Please help if the mapping is correct in table User and Offer .user_id column have null values ....:(
I'm not sure if these are only parts of the entities but in order for the entity to have an id, you need to provide it with one and annotate the relevant field as #Id.
I also use #GeneratedValue(strategy = GenerationType.IDENTITY) so each table will get it's own id (generated by Hibernate, you don't provide the id when you save a new entity and not a global id, otherwise let's say you add an Offer, you get id with value x, then add new User you get id with value x+1 and so on...
#Entity
#Data
#NoArgsConstructor
public class Offer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
#Data
#EqualsAndHashCode
#Entity
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch=FetchType.LAZY)
private Set<Offer> offers = new HashSet<Offer>();
}`
There is a table ORDERS from which were created views ORDER_VIEW_A and ORDER_VIEW_B.
I have created entity classes OrderViewA and OrderViewB where in each of them is mapping on entity named 'TransactionRecord'.
It is #OneToOne relationship.
There is column ORDER_ID in TRANSACTION_RECORD table and field orderId in TransactionRecord entity.
Field orderId is same for OrderViewA.id and for OrderViewB.id, cause views are selected from the same table.
My question is, how to map in Hibernate two views in OneToOne relationship with another entity by same field.
My code looks like this and it doesn't work in any way, Hibernate always end up with:
org.hibernate.AnnotationException: Referenced property not a
(One|Many)ToOne: com.example.app.model.TransactionRecord.orderId in
mappedBy of com.example.app.model.views.OrderViewA.orderViewA
#Entity
#Immutable
#Table(name = "ORDER_VIEW_A")
public class OrderViewA {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderARecord;
...
}
#Entity
#Immutable
#Table(name = "ORDER_VIEW_B")
public class OrderViewB {
#Id
#Column(name = "id")
private Long id;
...
#OneToOne(mappedBy = "orderId", fetch = FetchType.LAZY)
private IntegrationRecord orderBRecord;
...
}
#Entity
#Table(name = "TRANSACTION_RECORD")
public class TransactionRecord {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "order_id")
private Long orderId;
...
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewA orderViewA;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private OrderViewB orderViewB;
...
}
Here is my JPA structure:
Movie (look at cascade types):
#Entity
#Table(name = "movie")
public class Movie {
#Id
#Column(name = "movie_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//#OneToMany(cascade = CascadeType.ALL, mappedBy = "primaryKey.movie") //stack overflow
#OneToMany(mappedBy = "primaryKey.movie") //works fine
private List<Rating> ratings;
....
}
Rating:
#Entity
#Table(name = "rating")
#AssociationOverrides({#AssociationOverride(name = "primaryKey.movie", joinColumns = #JoinColumn(name = "movie_id")),
#AssociationOverride(name = "primaryKey.user", joinColumns = #JoinColumn(name = "imdb_user_id"))})
public class Rating {
#EmbeddedId
private RatingId primaryKey = new RatingId();
#Column(name = "rating_value")
private Integer ratingValue;
.....
}
RatingId:
#Embeddable
public class RatingId implements Serializable{
#ManyToOne
private Movie movie;
#ManyToOne
private User user;
}
When I call entityManager.merge(Movie movie) with CascadeType.ALL I get the StackOverflowError. If remove cascading, merge call doesn't throw the error. Where may be a problem?
I think this problem related to composite primary key. There is no error when merge performed on another entities with the same one-to-many relationship, but without composite id.
StackOverflow was caused by cyclic relations. To avoid exception I marked keys in many-to-many table as #ManyToOne(fetch = FetchType.LAZY).
That's how my tables look after modifications: https://stackoverflow.com/a/32544519/2089491
I have three entities. The first one is Company entity (see below).
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#JoinColumn(name = "company_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Employee> employees;
#OneToMany(mappedBy = "company")
private List<HistoryRecord> historyRecords;
The second is Employee
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#Column
String name;
#ManyToOne
#JoinColumn(name = "company_id", nullable = true)
private Company company;
#OneToMany(mappedBy = "employee")
private List<HistoryRecord> historyRecords;
Here is my HistoryRecord class
#Entity
public class HistoryRecord {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#ManyToOne
#JoinColumn(name = "company_id")
Employee employee;
#ManyToOne
#JoinColumn(name = "employee_id")
Company company;
#Column(name = "hire_date")
Date hireDate;
#Column(name = "resign_date")
Date resignDate;
When I'm trying to execute delete operation on Employee I'm getting this error
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [delete from employee where id=?]; constraint ["CONSTRAINT_12: PUBLIC.HISTORY_RECORD FOREIGN KEY(EMPLOYEE_ID) REFERENCES PUBLIC.EMPLOYEE(ID)
I think the problem is in cascade operation but I'm not sure. Is anybody can say how can I fix it?
The problem is due to the relationship of Employee -- HistoryRecord. The employee property on HistoryRecord is not nullable. If you want the HistoryRecord to be deleted when an employee is being deleted you need to add the cascade attribute to the #OneToMany(mappedBy = "employee") for historyRecords on Employee.
#OneToMany(mappedBy = "employee",cascade = CascadeType.REMOVE)
The ENDDM generates
ALTER TABLE "public"."project_group" ADD CONSTRAINT "mandant" FOREIGN KEY (mandant_id) REFERENCES "mandant" ("mandant_id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
for the database and
#OneToMany(cascade = CascadeType.REMOVE)
in Java.
Update your relation mapping in Company class is is missing cascade.
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<HistoryRecord> historyRecords;
I am trying to use an #JoinColumn as an #Id using JPA and I am getting SerializationExceptions, "Could not serialize."
UserRole.java:
#Entity
#Table(name = "authorities")
public class UserRole implements Serializable {
#Column(name = "authority")
private String role;
#Id
#ManyToOne
#JoinColumn(name = "username")
private User owner;
...
}
User.java:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
protected Long id;
#Column(name = "username")
protected String email;
#OneToMany(mappedBy = "owner", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected Set<UserRole> roles = new HashSet<UserRole>();
....
}
"username" is set up as a unique index in my Users table but not as the primary key.
Is there any way to make "username" act as the ID for UserRole? I don't want to introduce a numeric key in UserRole. Have I totally lost the plot here?
I am using MySQL and Hibernate under the hood.
That mapping doesn't really make sense. ID has to be unique, but ManyToOne says 'lots of these have the same User.'