OneToMany relationship using non-primary composite key - spring-boot

I have a table structure like this
Good Assignments Entity
#Embeddable
public class GoodAssignmentId {
String clientId,
String assignmentNumber;
LocalDate effectiveDate;
// Getters and setters
}
#Entity
#IdClass(GoodAssignmentId.class)
class GoodAssignment {
#id
String clientId;
#Id
String assignmentNumber;
#Id
LocalDate effectiveDate;
#OneToMany(mappedBy = "parentKey")
Set<GoodTasks> children;
String description;
// getters and setters goes below
}
Bad Assignments Entity
#Entity
#IdClass(BadAssignmentId.class)
class BadAssignment {
#id
String clientId;
#Id
String assignementNumber;
#Id
LocalDate effectiveDate;
String description;
// Getters and setters goes below
}
Child entities
#Entity
#IdClass(ParentTasksId.class)
#DiscriminatorColumn(name = "fieldD", discriminatorType = DiscriminatorType.STRING)
class ParentTasks {
#Id
String clientId;
#Id
String assignmentNumber;
#Id
String taskNumber;
}
#Entity
#DiscriminatorValue("G")
class GoodTasks extends ParentTasks {
#ManyToOne
#JoinColumns({
#JoinColumn(name = "clientId", referencedColumName = "clientId"),
#JoinColumn(name = "assignmentNumber", referencedColumName = "assignmentNumber")
})
GoodAssignments parentKey;
other fields....
}
This shows the error referencedColumnNames(fieldA, fieldB) of .... not mapped to a single property.
Unfortunately I cannot change the table structure. TableA has 3 columns as primary key, but only two of them forms the primary key in table B along with another field (fieldD via #DiscriminatorValue used by multiple classes). How can I map this relationship to get list of TableB items in TableA?
Example Class Diagram:
Effective date in the assignments tables is not part of tasks. So this is not a perfect relationship in JPA terms. It's a legacy design which cannot be changed for some reasons.

Related

Composite primary key and ManyToOne hibernate

I'm learning Hibernate and I'm trying to make the Mapping work. My entities are as follows
Department:
#Entity
public class Department {
#Id
#GeneratedValue
private Integer id;
private String name;
private String hqLocation;
// getters and setters omitted for brevity
}
WorkerId:
#Embeddable
public class WorkerId implements Serializable {
private Integer workerId;
private Integer deptId;
// getters and setters omitted for brevity
}
Worker:
#Entity
public class Worker {
#EmbeddedId
private WorkerId id;
private String name;
private Integer age;
// How to make #ManyToOne mapping work?
private Department department;
// getters and setters omitted for brevity
}
Question: How to make #ManyToOne on the field private Department department; work? Simply adding the annotation results private Department department; as null.
I think you want to use a "derived identity"; so you should make Worker.department like this:
#Entity
public class Worker {
#EmbeddedId
private WorkerId id;
private String name;
private Integer age;
#MapsId("deptId")
#ManyToOne // <<<< edit
private Department department;
// getters and setters omitted for brevity
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Question really is, which entity should be the owner of the relationship? would you like to map bidirectional or single way?
Here is the bidirectional example
#OneToMany(
fetch = FetchType.EAGER,
mappedBy = "department",
orphanRemoval = true,
cascade = CascadeType.ALL)
private List<Worker> workers;
#JoinColumn(name = "department_id", nullable = false)
#ManyToOne(targetEntity = Department.class, fetch = FetchType.LAZY)
private Department department;
fetch types are optional and depend on the use case

#ManyToOne Referencing a composite key in JPA

I have following entities
#Entity
#IdClass(SubjectId.class)
class Subject {
#Id
String name;
#Id
String volume;
........
}
class SubjectId {
String name;
String volume;
//constructor, getters and setters
..........
}
#Entity
class Student {
#Id
String studentId;
String subject;
String subjectVolume;
}
I want to map the fields subject and subjectVolume of class Student to composite primary key of class Subject as a #ManyToOne relationship. But I don't know what should I pass inside #ManyToOne(?).
Any help would be appreciated, thanks.
Edit:
I want to use the columns subjectName and subjectVolume as entity fields in class Student as well. I don't want to do student.getSubject().getSubjectName() instead I want student.getSubjectName().
You could just declare the relation this way (instead of declaring the fk fields):
class Student{
#Id
String studentId;
#ManyToOne // this is sufficient create foreign-key columns in the Student-table
Subject subject;
}
The generated columns of the Student table will have these names by default:
In case you need different column names you should look for the #JoinColumn annotation.
edit: to be able to directly call student.getSubjectName() you could still decide to include single parts of the composite foreign key additionally as entity properties, in this case you need to make sure to declare the second (duplicate) column mapping with insertable=false and updatable=false, since its value is already managed by the #ManyToOne fk:
#Entity
static class Student {
#Id
String studentId;
#ManyToOne
Subject subject;
#Column(name = "subject_name", insertable = false, updatable = false)
String subjectName;
}
However, I'd probably prefer simply declaring a custom getSubjectName() getter which just returns subject.getName().

spring data - how to make unique constraint with custom logic?

using spring data, I created User 1:N UserDog N:1 Dog relation. Both 1:N relations are unidirectional #ManyToOne with UserDog being the custom relation table.
User entity:
#Entity
public class User {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
Dog entity:
#Entity
public class Dog {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
User dog relation table:
#Entity
public class UserDog {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private Dog dog;
#Column(nullable = false)
private Instant createdOn = Instant.now();
#Column
private Instant disabledOn;
}
Use case
Use case is to store history of User-Dog bindings, where the concrete Dog can be bound only to one User at the time. That's why I added createdOn and disabledOn columns to UserDog. disabledOn being null indicates that the relation is active and the Dog can't be assigned another User. If disabledOn is not null, then the record is stored only for evidence purposes and the Dog can be assigned to the same or another User again.
Question
How to ensure that the combination of Dog's id and disabledOn being null is unique in UserDog table?
In pseudo code I want something like this:
#Entity
#UniqueConstraint({#UniqueConstraint(this.dog.id), #NullConstraint(this.disabledOn)})
public class UserDog {...}
You can simply create a unique constraint for dogId and disabledOn.
It does add the limitation that no two relationships may end at the same time but this seems to fit your use case.

Usual field as foreign key

I have two tables. I want to make between them relationship, but the thing is that the child table connects to an attribute in a parent node, which is not a PK. How can I assign a non-PK field as a FK for a table?
Here are the tables. User Information:
#Entity
#Table(name="user")
public class userinformation implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="USER_ID")
private int uID;
#Column(name="LIB_ID")
private String libID;
//Other attributes
}
Lib Information
#Entity
#Table(name="libinfo")
public class Auth {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="AUTH_ID")
private int authID;
#Column(name="LIB_ID")
private String lib_ID;
//Other attributes
}
They both should be linked through libID (surely unique). Any idea how to implement it correctly?
Given:
class User {
#Column(name = "lib_id")
private String libID;
}
you must map the Auth entity as:
class Auth {
#JoinColumn(name = "lib_id", referencedColumnName = "lib_id")
#ManyToOne
private User user;
}
Basically, referencedColumnName is used to inform the JPA provider that it should use a column other than the primary key column of the referenced entity (which is used by default with #ManyToOne mappings).

Links to Embedded entities in Spring Data Rest

I have the following entities defined in my project:
Country
#Entity
#Data
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
String name;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
List<City> cities = new ArrayList<City>();
}
City
#Entity
#Data
public class City {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
String name;
#ManyToOne
Country country;
}
Person
#Entity
#Data
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column
String name;
#Embedded
Address address = new Address();
}
Address
#Data
public class Address {
#Column
String line;
#ManyToOne
Country country;
#ManyToOne
City city;
}
I have also repositories defined for Person, Country and City.
When I make a GET request to /persons/1 I get the following result:
{
"name":null,
"address":{
"line":"Address1"
},
"_links":{
"self":{
"href":"http://localhost:8080/persons/1"
},
"city":{
"href":"http://localhost:8080/persons/1/city"
},
"country":{
"href":"http://localhost:8080/persons/1/country"
}
}
}
I suspect that since address is an embedded object, the generated links to country and city are wrong. They don't return anything although city and country values are present. What should the correct links be?
Are embedded objects not supported by Spring Data Rest?
Possible solutions:
move associations to the parent entity
promote the embeddable into a separate entity resource
add ResourceProcessor to remove those links
add a custom controller to handle those links
UPDATE: This seems to be already fixed in Spring-DATA-REST v2.1. See DATAREST-262.

Resources