JPA Many to Many with Intermediate Table and MapKey* - java

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.

Related

How can I map these entities using hibernate? Need to map one to many relationship. Foreign key is not updating

In this case, I have 2 entities (Users table and UploadRecord table). I need to map a one-to-many relationship because one user can have many upload records. I need to use UserId as the primary key in the Users table and a foreign key as the UploadRecord table.
I tried using this code but the UploadRecord table fk_UserId is not updating. How to fix this issue?
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_UserId", referencedColumnName = "UserId")
private List<UploadRecord> uploadRecord;
I wrote Users entity class and UploadRecord entity class as follows.
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name= "UserId")
private Long UserId;
#Column(nullable = false, unique = true, length = 45)
private String email;
#Column(name = "fullName", nullable = false, length = 20)
private String fullName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_UserId", referencedColumnName = "UserId")
private List<UploadRecord> uploadRecord;
//Getters and setters
#Entity
#Table(name = "uploadrecord")
public class UploadRecord {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uploadRecordId;
#Column(nullable = false, unique = false, length = 1000)
private String fileName;
//Getters and setters
It seems you haven't finished modelling the relationship between these two entities.
Edit your models like this:
User:
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<UploadRecord> uploadRecords;
UploadRecord :
#ManyToOne
#JoinColumn(name = "userId")
private User user;
More details for modelling relations: Baeldung
Moreover keep an eye on naming convention:
UserId -> userId
uploadRecord -> uploadRecords (Lists, Sets, ...) -> plural

HIbernate doesn't create Foreign key for #OneToOne

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.

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.

hibernate one to many mapping with annotations - getting same values in the list

I am Linking User table with the Application Access. Here one User can have access to many applications.
I have done the mapping successfully with the below piece of code.
User entity object:
#Entity
#Table(name = "USER_TBL", uniqueConstraints = { #UniqueConstraint(columnNames = "USER_NAME") })
public class User implements Serializable {
.....
#Id
#GeneratedValue
#Column(name = "USER_ID", unique = true, nullable = false)
private Integer userId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> userAssociatedApplications = new ArrayList<UserAppAssociation>();
Getter and setter for userAssociatedApplications
}
Application access object:
#Entity
#Table(name="APPLICATION_ASSOC_TBL")
public class UserAppAssociation implements Serializable{
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#Column(name = "application_id")
private Integer appId;
#Column(name = "user_type_id")
private Integer userTypeId;
...
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
..
getters and setters
}
Issue:
I am getting the same values in the Application List ('userAssociatedApplications'). Though i have different values in the application access table, I get the same values in the list. The first row value is repeated in the list.
DB:
I have 'User' table and the mapping is with application access
User table: USER_TBL
Columns
user_id name phone
Application access table : APPLICATION_ASSOC_TBL
Columns
User_id application_id and User_type
Note - no primary key in this table
Sample data:
User_id application_id User_type
1 1 1
1 2 1
1 3 1
Issue: I am getting the first value 1,1,1 in the list thrice.
Expected: List should be with 3 different values
Kindly help. I am not sure whether i am missing anyting in the annotation mapping.
Looks like a problem with this
#Id
#Column(name="user_id", unique=true, nullable=false)
private Integer userId;
#ManyToOne
#JoinColumn(name="USER_ID",insertable=false,updatable=false)
private User user;
Try to use this mapping. Please, refer this as a guide for names and don't use unnecessary annotations
#Entity
#Table(name = "xxx_users", uniqueConstraints = { #UniqueConstraint(columnNames = "f_name") })
public class User {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<UserAppAssociation> applications = new ArrayList<UserAppAssociation>();
}
#Entity
#Table(name="xxx_user_applications")
public class UserAppAssociation {
#Id
#GeneratedValue
#Column(name = "f_id")
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="fk_user")
private User user;
}

JPA - Can an #JoinColumn be an #Id as well? SerializationException occurs

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.'

Categories

Resources