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.
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 have a problem with DiscriminatorColumn and DiscriminatorValue.
My expectation is :
-TypeOne, TypeTwo, TypeThree are options related to the User.
-A user can have TypeOne only 1 row.
-A user can have TypeTwo or TypeThree multiple rows.
-I used Pageable for search criteria and sorting by typeOneEntity.optionCode
User table.
|id|user_id|
|-|-|
|1114|a#a.com|
UserOption table.
|id|user_id|option_code|option_type_name|
|-|-|-|-|
|1|1114|CODE_1|TYPE_1|
|2|1114|CODE_2|TYPE_2|
|3|1114|CODE_3|TYPE_2|
|4|1114|CODE_4|TYPE_3|
Ps. User.id = UserOption.user_id
I have to create three entities map to this data.
#Entity
#Table(name = "user")
public class User{
#Column()
private Long id;
#Column()
private String userId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private TypeOne typeOneEntity;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private Set<TypeTwo> typeTwoEntities;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "id", referencedColumnName = "user_id", insertable = false, updatable = false)
private Set<TypeThree> typeThreeEntities;
}
#Table(name = "user_option")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "option_type_name")
public abstract class UserOption{
#Column
private Long id;
#Column(name = "user_id", nullable = false)
private Long userId;
#Column
private String optionCode;
}
#Entity
#DiscriminatorValue(value = "TYPE_1")
public class TypeOne extends UserOption {
}
#Entity
#DiscriminatorValue(value = "TYPE_2")
public class TypeTwo extends UserOption {
}
#Entity
#DiscriminatorValue(value = "TYPE_3")
public class TypeThree extends UserOption {
}
After I build and try to find data, I found two issues.
1.On typeOneEntity(#OneToOne) is null.
2.On #OneToMany : I got this error ORA-01722: invalid number (error message from Hibernate)
This Entity model that I create it correct or not?
Or it need to change entity model.
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 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
Not sure if this is possible but trying to map WorkflowInstancePlayer player which is related based on two other entity mappings, WorkActionClass and WorkflowInstance in the entity below.
public class Action implements Serializable {
#Id
private Long action_id;
#ManyToOne
#JoinColumn(name = "work_action_class_id", referencedColumnName = "work_action_class_id")
private WorkActionClass workActionClass;
#ManyToOne
#JoinColumn(name = "workflow_instance_id", referencedColumnName = "workflow_instance_id")
private WorkflowInstance workflowInstance;
UPDATE: How can I map to a WorkflowInstancePlayer player?????
#ManyToOne
#JoinColumns( {
#JoinColumn(name = "workflow_instance_id", referencedColumnName = "workflow_instance_id", insertable = false, updatable = false),
#JoinColumn(name = "workActionClass.role_class_id", referencedColumnName = "role_class_id", insertable = false, updatable = false)
})
private WorkflowInstancePlayer player;
The workflowInstancePlayer is derived based on workflow_instance_id and role_class_id but role_class_id is really an attibute of workActionClass mapped above (workActionClass.role_class_id)
public class WorkflowInstancePlayer implements Serializable {
#Id
private WorkflowInstance workflowInstance;
#Id
private RoleClass roleClass;
#ManyToOne
#JoinColumn(name = "badge", referencedColumnName = "badge")
private Employee employee;
public class WorkActionClass implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "trx_seq")
private Long work_action_class_id;
#ManyToOne
#JoinColumn(name = "role_class_id")
private RoleClass roleClass;
Example data would be:
Action
------
Id = 10
work_action_class_id = 7
workflow_instance_id = 2
WorkActionClass
---------------
Id = 7
role_name = reviewer
role_class_id = 3
WorkflowInstancePlayer
----------------------
workflow_instance_id = 2
role_class_id = 3
badge = 111222
So in the Action Entity, I'll know the Workflow Instance Player is Employee with Id 111222 without actually storing the badge in the Action table.
UPDATE
Based on Vlad's post I tweaked it to be
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula=#JoinFormula(value="(SELECT a.role_class_id FROM (Use Table Name not Entity Name here) a WHERE a.work_action_class_id = work_action_class_id)", referencedColumnName="role_class_id")),
#JoinColumnOrFormula(column = #JoinColumn(name="workflow_instance_id", referencedColumnName="workflow_instance_id"))
})
Try with this mapping:
#ManyToOne
#JoinColumn(
name = "workflow_instance_id",
referencedColumnName = "workflow_instance_id",
insertable = false,
updatable = false
)
private WorkflowInstance workflowInstance;
#ManyToOne
#JoinColumnOrFormula(
formula = #JoinFormula(
value="(SELECT a.work_action_class_id FROM WorkActionClass a WHERE a.role_class_id = role_class_id)",
referencedColumnName = "work_action_class_id"
)
)
private WorkActionClass workActionClass;
#ManyToOne
#JoinColumns( {
#JoinColumn(
name = "workflow_instance_id",
referencedColumnName = "workflow_instance_id"),
#JoinColumn(
name = "role_class_id",
referencedColumnName = "role_class_id")
})
private WorkflowInstancePlayer player;