HIbernate doesn't create Foreign key for #OneToOne - java

I have two tables:
#Entity
public class TestEntity {
#Id
#Column(name = "id")
private UUID id;
#OneToOne(targetEntity = InfoEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName = "id")
private InfoEntity info;
...
}
#Entity
public class InfoEntity {
#Id
#Column(name = "id")
private UUID id;
#OneToOne(mappedBy = "info")
private TestEntity test;
...
}
Basically I want to define a Foreign Key from TestEntity to InfoEntity by id fields which are the same in both tables. The problem is that when I check the database in IntelliJ Idea I don't see any Foreign Keys in keys section (checked both tables), only their PK's. Is something wrong with this code? I already set the property as it suggested in another similar question:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect

The problem is that name in JoinColumn has value id. Change it to info_id for example:
#OneToOne(targetEntity = InfoEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name="info_id", referencedColumnName = "id")
private InfoEntity info;
You TestEntity already has a column id, so it has to has another name.

Related

JPA Many to Many with Intermediate Table and MapKey*

I'm struggling to setup the following JPA mapping (extra cols, etc. omitted for brevity)
----
id: primary key
organization
------------
id: primary key
z
role_assignment
---------------
role_assignment_id: primary key
user_id: fk -> user.id
organization_id: fk -> organization.id
role
(user_id, organization_id) is unique
The mapping needed would be on Organization class:
Map<User, RoleAssignment> roleAssignments
I can get close with something like:
class User {
#Id
#GeneratedValue
private long id;
}
class RoleAssignment {
#Id
#GeneratedValue
#Column(name = "role_assignment_id")
private long id;
#Column(name = "role")
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "organization_id")
private Organization organization;
}
class Organization {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "user", orphanRemoval = true, cascade = CascadeType.ALL)
#MapKey(name = "user")
private Map<User, RoleAssignment> roleAssignments;
}
I've tried several configurations with MapKey, MapKeyJoinColumn, etc, and the best I get is a map with one null key mapped to a single value (despite multiple entries in the database).
Any help on setting this up would be greatly appreciated.
Update
I think I omitted some key info trying to be concise. The following paints a more clear picture:
----
id: primary key
organization
------------
id: primary key
role
----
id: primary key
role_assignment
---------------
role_assignment_id: primary key
user_id: fk -> user.id
organization_id: fk -> organization.id
role_id: fk -> role
(user_id, organization_id) is unique and can be made primary key if needed
The mapping needed would be on Organization class:
Map<User, RoleAssignment> roleAssignments
I can get close with something like:
class User {
#Id
#GeneratedValue
private long id;
}
class RoleAssignment {
// this could be converted to an #EmbeddedId if that helps
#Id
#GeneratedValue
#Column(name = "role_assignment_id")
private long id;
#Column(name = "role")
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "organization_id")
private Organization organization;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
class Organization {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "organization", orphanRemoval = true, cascade = CascadeType.ALL)
#MapKey(name = "user") // <-- this is the piece I can't get to work
private Map<User, RoleAssignment> roleAssignments;
}
I'm able to get a basic mapping to List<RoleAssignment> to work, but the logic of the rest of the application requires the Map mapping I described.

JPA/Hibernate/SpringBoot: Inserting #OneToMany relationship entities

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
}

Hibernate mapping join on one column of constants table

I have 2 tables, the first one is quite variable, the second one contains only constants:
USER.ID USER.NAME USER.USER_TYPE (FK on USER_TYPE.ID)
INT VARCHAR(64) INT(1)
----------------------------------
1 Alex 3
2 Jane 1
3 Carl 3
USER_TYPE.ID USER_TYPE.VALUE
INT(1) VARCHAR(64)
------------------------------
1 PENDING
2 REGISTERED
3 BANNED
4 ACTIVE
The foreign key USER.USER_TYPE is required and refering to a primary key USER_TYPE.ID in table USER_TYPE (one-to-one relation). Here is my mapping in Hibernate.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE")
private UserType userType;
}
UserType.java
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "VALUE")
private String value;
}
My goal is to keep the enumerated values in the database. How to map UserType's value instead of id to User and validate it? I want to pass the constant VALUE to the String instead of its ID.
private String userType;
The expected result of the first user would be:
User[id=1, name=Alex, userType=Banned]
User[id=2, name=Jane, userType=Pending]
User[id=3, name=Carl, userType=Banned]
My attempt was to use this annotation on definition of table twice with both colums switched
#SecondaryTable(name="USER_TYPE",
pkJoinColumns={#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_TYPE")}
)
and get the VALUE with
#Column(table="USER_TYPE", name="VALUE")
private String UserType;
however it leads to the error
Unable to find column with logical name: USER_TYPE in org.hibernate.mapping.Table(USER) and its related supertables and secondary tables
First you need to change the relation from #OneToOne to #ManyToOne as UserType can be used by one or many User and User can have one and one UserType.
Secondly use referencedColumnName which references :
The name of the column referenced by this foreign key column.
So User entity will be:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE", referencedColumnName = "VALUE")
private UserType userType;
}
In UserType you should apply a unique constraint using #NaturalId to value field + do not provide its setter, to prevent duplicate values as It may lead to inconsistency:
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#NaturalId
#Column(name = "VALUE")
private String value;
}
Hope it solves the issue!
Enumerations could be simpler:
enum UserType {
PENDING,
REGISTERED,
BANNED,
ACTIVE
}
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#javax.persistence.Enumerated
private UserType userType;
}
If you really need separated table and #OneToOne relation, you can use #Formula from Hibernate:
#Formula("(select ut.value from user_type ut where ut.ID = USER_TYPE)")
private String userType;
For this really special requirement you could use SecondaryTable annotation.
That is, you don't need UserType entity, but declare attribute userType as String in User entity with column mapping to the secondary table "USER_TYPE".
First of all, I suggest you use ManyToOne relation. and Not CascadeType.ALL if you are not planning update or delete on USER_TYPE table.
If you do not need adding new UserTypes frequently use enum for it. It will just work as you want.
Second solution: As long as fetch = FetchType.EAGER you can add A transient field and return value of UserType in getter.

Override the Foreign Key Name pointing to Composite Key JPA/Hibernate

I'm using spring-boot 1.5.4 with spring-data-jpa and I'm trying to override the auto generated foreign key name during spring.jpa.hibernate.ddl-auto=create.
For simple id, I was able to override it: simple_fk
Hibernate: alter table my_entity add constraint simple_fk foreign key (simple_id) references simple
But not for foreign key with composite id: FKms12cl9ma3dk8egqok1dasnfq
Hibernate: alter table my_entity add constraint FKms12cl9ma3dk8egqok1dasnfq foreign key (composite_id1, composite_id2) references composite
What is wrong with my code? I also tried #PrimaryKeyJoinColumn.
Please see the class definitions below.
#Entity
public class Simple {
#Id
private long id;
}
#Entity
public class Composite {
#Id
private CompositeId id;
}
#Embeddable
public class CompositeId {
#Column
private long id1;
#Column
private long id2;
}
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"),
name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2")
})
private Composite composite;
}
This is a known issue with the Hibernate which was fix in version 5.2.8
So there are two ways to fix it: Either you update the Hibernate to the version 5.2.8 or up by adding
<hibernate.version>5.2.10.Final</hibernate.version>
to your pom.xml, which basically will update the Hibernate to the latest version.
or if Hibernate update is not possible or is just too risky you can add legacy/deprecated #org.hibernate.annotations.ForeignKey(name = "composite_fk") annotation on your
composite field which will make you code look like
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"), name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2") })
#org.hibernate.annotations.ForeignKey(name = "composite_fk")
private Composite composite;
}

How to resolve cascade deleting in Hibernate?

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;

Categories

Resources