I have a following example I want to create a lazy #ManyToOne relation between Car and CarAchievement tables using multiple join columns (example of class below)
#Entity
#Table(name = "CAR")
public class Car implements Serializable {
#Id
#GenericGenerator(name = "SEQ_CAR", strategy = "sequence",
parameters = {
#org.hibernate.annotations.Parameter(
name = "sequence",
value = "SEQ_CAR"
)
}
)
#GeneratedValue(generator = "SEQ_CAR")
#Column(name = "ID")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumns({
#JoinColumn(name = "region", referencedColumnName = "region", updatable = false, insertable = false),
#JoinColumn(name = "model", referencedColumnName = "model", updatable = false, insertable = false),
#JoinColumn(name = "year", referencedColumnName = "type", updatable = false, insertable = false),
#JoinColumn(name = "type", referencedColumnName = "year", updatable = false, insertable = false)
})
#JsonIgnore
private CarAchievement carAchievement;
}
This relation works fine but it seems not to be a LazyFetch, every query for a CAR seems to be fetching CarAchievement automatically even when its not specified to fetch this relation
Hibernate version: 4.3.10.Final
Related
I have a couple of tables with relation as in the image below
I created hibernate data model as follows
#Entity
#Table(name = "SUBJECT")
public class Subject {
#Column(name = "NAME")
private String name;
#Column(name = "ADDRESS")
private String address;
#Column(name = "CLIENT_ID")
private String clientId;
#OneToMany(mappedBy = "subject", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<SSI> SSIs;
// getters and setters
...
}
#Entity
#Table(name = "SUBJECT_IDENTIFIER")
public class SubjectIdentifier {
#Column(name = "VALUE")
private String value;
#Column(name = "AUTHORITY")
private String authority;
#Column(name = "TYPE")
private String type;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "SUBJECT_ID", referencedColumnName = "ID", insertable = true,
updatable = true,
#JoinColumn(name = "CLIENT_ID", referencedColumnName = "CLIENT_ID", insertable =
true, updatable = true)
})
private Subject subject;
// getters and setters
...
}
#Entity
#Table(name = "SSI")
public class SSI {
#ManyToOne
#JoinColumns({
#JoinColumn(name = "SUBJECT_ID", referencedColumnName = "ID", insertable = true,
updatable = true),
#JoinColumn(name = "CLIENT_ID", referencedColumnName = "CLIENT_ID", insertable =
true, updatable = true)
})
private Subject subject;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "SUBJECT_IDENTIFIER_ID", referencedColumnName = "ID", insertable = true,
updatable = true),
#JoinColumn(name = "CLIENT_ID", referencedColumnName = "CLIENT_ID", insertable =
true, updatable = true)
})
private SubjectIdentifier subjectIdentifier;
// getters and setters
...
}
I intend to create the entities as follows
...
Subject s = new Subject();
//.. initialization of s goes here
SubjectIdentifier si = new SubjectIdentifier();
//.. initialization of si goes here
SSI ssi = new SSI();
ssi.setSubject(s);
ssi.setSubjectIdentifier(si);
s.setSSI(ssi);
...
emProvider.get().persist(s);
When I run this, I get following error
org.hibernate.MappingException: Repeated column in mapping for entity: *.SSI column: CLIENT_ID (should be mapped with insert="false" update="false")
If I set insert="false" update="false" for CLIENT_ID, it would error again about mixing of insert & update with other column in the #Joincolumns
If I set insert="false" update="false" for all the #JoinColumns then it will not persist the objects.
How to really handle this kind of entity creation?
That's not so easy. If you want that, you have to introduce another attribute for storing the client id and maintain this denormalization:
#Entity
#Table(name = "SSI")
public class SSI {
#Column(name = "CLIENT_ID")
private String clientId;
#Column(name = "SUBJECT_ID")
private String subjectId;
#Column(name = "SUBJECT_IDENTIFIER_ID")
private String subjectIdentifierId;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "SUBJECT_ID", referencedColumnName = "ID", insertable = false,
updatable = false),
#JoinColumn(name = "CLIENT_ID", referencedColumnName = "CLIENT_ID", insertable =
false, updatable = false)
})
private Subject subject;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "SUBJECT_IDENTIFIER_ID", referencedColumnName = "ID", insertable = false,
updatable = false),
#JoinColumn(name = "CLIENT_ID", referencedColumnName = "CLIENT_ID", insertable =
false, updatable = false)
})
private SubjectIdentifier subjectIdentifier;
// getters and setters
...
}
I'm trying to save a complex entity with Hibernate where we have multiple entities with composite keys.
When I try tosave it seems that Hibernate it is not retrieving correctly the values from some columns on child entities which have composite key hence postgre is returning a not-null violation error.
#Entity
#Table(name = "activities", schema = "ptw")
#Data
#TypeDefs({
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class),
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class Activity extends AuditAtBy implements Serializable {
#EmbeddedId
private CommonId commonId;
#MapsId("siteId")
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
#Type(type = "pg-id-uuid")
#JoinColumn(name="site_id",referencedColumnName = "id", columnDefinition = "uuid", updatable = false)
private Site site;
#ManyToOne()
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula = #JoinFormula(value="location_id", referencedColumnName = "id")),
#JoinColumnOrFormula(formula = #JoinFormula(value="site_id", referencedColumnName = "site_id"))})
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Location location;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "job_pack_id", referencedColumnName = "id", columnDefinition = "uuid", insertable = false, updatable = false),
#JoinColumn( name = "site_id", referencedColumnName="site_id", columnDefinition = "uuid", insertable = false, updatable = false)
})
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private JobPack jobPack;
#Column(name = "permit_number", nullable = false, length = 10)
private String permitNumber;
#Column(name = "order_number", nullable = false)
private short orderNumber;
#Column(name = "location_name", length = 200)
private String locationName;
**#ManyToOne
#JoinColumns({
#JoinColumn(name = "state_id", referencedColumnName="id", columnDefinition = "uuid", insertable = false, updatable = false),
#JoinColumn(name = "site_id", referencedColumnName="site_id", columnDefinition = "uuid", insertable = false, updatable = false)
})
#Type(type = "pg-id-uuid")
private ActivityState state;**
#ManyToOne
#JoinColumns({
#JoinColumn(name = "activity_type_id", referencedColumnName="id", columnDefinition = "uuid", insertable = false, updatable = false),
#JoinColumn(name = "site_id", referencedColumnName="site_id", columnDefinition = "uuid", insertable = false, updatable = false)
})
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private ActivityType activityType;
public UUID getId(){
return this.getCommonId().getId();
}
public void setId(UUID id){
if (this.getCommonId() == null) {
this.setCommonId(new CommonId());
}
this.getCommonId().setId(id);
}
public void setSite(Site site){
this.site = site;
if (this.getCommonId() == null) {
this.setCommonId(new CommonId());
}
this.getCommonId().setSiteId(site.getId());
}
}
The setId/getId/ setSite are overriden in order to update the entity when using a mapper to convert from the DTO to the Entity
The ActivityState is as follows:
#Entity
#Table(name = "activity_states", schema = "ptw")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class ActivityState extends AuditAtBy implements Serializable {
#EmbeddedId
private CommonId commonId;
#MapsId("siteId")
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#Column(nullable = false, length = 50)
private String name;
#Column(name = "icon_id", nullable = false)
private short iconId;
#Column(name = "is_initial", nullable = false)
private boolean isInitial;
#Column(name = "order_number", nullable = false)
private short orderNumber;
public UUID getId(){
return this.getCommonId().getId();
}
public void setId(UUID id){
if (this.getCommonId() == null) {
this.setCommonId(new CommonId());
}
this.getCommonId().setId(id);
}
public void setSite(Site site){
this.site = site;
if (this.getCommonId() == null) {
this.setCommonId(new CommonId());
}
this.getCommonId().setSiteId(site.getId());
}
}
When I try to save the exception is:
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "state_id" violates not-null constraint
Detail: Failing row contains (c821ff72-de93-4c03-abf5-e18347c29955, null, 0, 5081790f-19ed-44e0-be17-94f94aed878b, null, null, test, test, null, 1, 1, f, N/A, 1, null, null, null, null, null, null, null, null, null, null, null, f, {"title": "test", "DateTable": {"timeTo": "", "timeFrom": "", "v..., 2021-12-09 13:46:02.829157+01, 2021-12-09 13:46:02.829157+01, 7b0702c7-9f11-4a92-bfdf-7f98eb8ac94d, 7b0702c7-9f11-4a92-bfdf-7f98eb8ac94d, null, null, f).
I have no idea about how to solve although I have tried multiple things changing the mappings and relationships.
I know that composites keys are not the best approach but we have a multitenancy system where the best approach to keep data isolated was this one.
The answer was basically the first answer to this post: Should Hibernate be able to handle overlapping foreign keys?
Basically changing the mapping to be with the actual column as column and the one repeated in other entities as formula it is working now:
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name="state_id", referencedColumnName = "id")),
#JoinColumnOrFormula(formula = #JoinFormula(value="site_id", referencedColumnName = "site_id")) })
I want to create a double self-reference entity using an extra join table. I tried thus the following :
#Entity
#Table(name = "entity_a", schema="schema_a")
public class EntityA{
#Id
#Column(name = "id", unique = true, nullable = false)
private UUID id = UUID.randomUUID();
//skipped source code...
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "origin_child",
joinColumns =
{ #JoinColumn(name = "origin_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns =
{ #JoinColumn(name = "child_id", referencedColumnName = "id", nullable = false)})
private EntityA child;
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "origin_child",
joinColumns =
{ #JoinColumn(name = "child_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns =
{ #JoinColumn(name = "origin_id", referencedColumnName = "id", nullable = false)})
private EntityA origin;
//skipped source code...
}
When running my code I get the following error : org.hibernate.boot.spi.InFlightMetadataCollector$DuplicateSecondaryTableException: Table with that name [origin_child] already associated with entity
I tried thus to remove the origin field from EntityA and it worked. Now I am trying to figure out a way to join my EntityA.id and EntityA.origin_id over the already existing origin_child.child_id and origin_child.origin_id. Any idea how may I achieve that. Any alternative or better solution is more than welcome.
UPDATE :
I have tried the #JoinColumn alternative as following :
#OneToOne(fetch = FetchType.LAZY, mappedBy = "child")
private EntityA origin;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "child_id", referencedColumnName = "id", nullable = true)
private EntityA child;
Now when trying to update both origin and child instances :
child.setOrigin(origin);
entityARepository.save(child);
origin.setChild(child);
entityARepository.save(origin);
I get java.lang.StackOverflowError due to infinite recursion.
Any work around please?
I have manyToMany relationship mapping and couldn't get it to work. I have read many posts and articles and couldn't figure this one out. If anyone has some idea please share.
I have tried to simplify diagram and code as much.
My database is designed like this:
My entities look like this (at least final attempt before asking):
Client:
#Entity
#Table(name = "client")
public class Client implements Serializable {
#Id
#Column(name = "client_id")
private int id;
... other fields
}
Project:
#Entity
#Table(name = "project")
public class Project implements Serializable {
#EmbeddedId
private ProjectId id;
... other fields
#Embeddable
class ProjectId implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id", insertable = false, updatable = false)
private Client client;
#Column(name = "project_id")
private int projectId;
}
}
User:
#Entity
#Table(name = "user")
public class User implements Serializable {
#EmbeddedId
private UserId id;
... other fields
#Embeddable
class UserId implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id", insertable = false, updatable = false)
private Client client;
#Column(name = "user_id")
private int userId;
}
}
ProjectUser:
#Entity
#Table(name = "project_user")
public class ProjectUser implements Serializable {
#EmbeddedId
private ProjectUserId id;
... other fields
#Embeddable
class ProjectUserId implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id", insertable = false, updatable = false)
private Client client;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "client_id", referencedColumnName = "client_id", insertable = false, updatable = false),
#JoinColumn(name = "project_id", referencedColumnName = "project_id", insertable = false, updatable = false) })
private Project project;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "client_id", referencedColumnName = "client_id", insertable = false, updatable = false),
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable = false, updatable = false) })
private User user;
}
}
Before adding ProjectUser entity everything is working fine.
Now when I'm starting server it says:
Repeated column in mapping for entity: ProjectUser column: client_id
(should be mapped with insert=\"false\" update=\"false\")"}}
So, the question is how do I make this work?
EDIT:
Java application will be mostly REST services providing data. Database design is as is. It has logical sense and most of the business logic will be in database. We have people with very good DB knowledge working on this and it would not make much sense changing database design because of JPA/Hibernate limitations.
The code below will let hibernate create a structure for your entities:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "client_id", insertable = false, updatable = false)
private Client client;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "project_client_id", referencedColumnName = "client_id", insertable = false, updatable = false),
#JoinColumn(name = "project_id", referencedColumnName = "project_id", insertable = false, updatable = false) })
private Project project;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "user_client_id", referencedColumnName = "client_id", insertable = false, updatable = false),
#JoinColumn(name = "user_id", referencedColumnName = "user_id", insertable = false, updatable = false) })
private User user;
But to make a scheme as on the picture in your question, get rid of all these EmbeddedId. Use simple ids and add validation in your code, if you want project and user inside your ProjectUser have the same client_id.
I have a project where Hibernate is used to manage entities. Multiple #ManyToMany relations can exists between two tables. So basically i have code like this:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "movies_screenplay_authors",
joinColumns = #JoinColumn(name = "movie_id", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "staff_id", nullable = false, updatable = false))
private Set<Staff> screenplayAuthors = Sets.newHashSet();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "movies_story_authors",
joinColumns = #JoinColumn(name = "movie_id", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "staff_id", nullable = false, updatable = false))
private Set<Staff> storyAuthors = Sets.newHashSet();
Now, I would like to store those relations inside a single table, with some additional column describing relation type. So basically, I would like to have something like this (pseudo-code used):
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "movies_staff",
joinColumns = #JoinColumn(name = "movie_id", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "staff_id", nullable = false, updatable = false),
joinCriteria = #JoinCriteria(columnName = "staff_type", value = StaffType.SCREENPLAY_AUTHOR, enumType = EnumType.STRING))
private Set<Staff> screenplayAuthors = Sets.newHashSet();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "movies_staff",
joinColumns = #JoinColumn(name = "movie_id", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "staff_id", nullable = false, updatable = false),
joinCriteria = #JoinCriteria(columnName = "staff_type", value = StaffType.STORY_AUTHOR, enumType = EnumType.STRING))
private Set<Staff> storyAuthors = Sets.newHashSet();
Is this even possible, in Hibernate, or anywhere in Java world?
You can add after each #JoinTable annotation something like this:
#Fetch(FetchMode.SUBSELECT)
And its all.