Three-way hibernate ORM mapping - how to? - java

I have a three SQL tables:
create table users (
id serial primary key,
name text not null unique
);
create table posts (
id serial primary key,
data text not null,
authorId integer not null references users (id)
);
create table ratings (
id serial primary key,
name text not null unique
);
One post can have only one author, so users <-> posts relation is already established in normal form (correct me if i am wrong).
Ratings are pre-defined constants like "bad", "good" or "awesome", with (in real case) additional data as rating value, description or other fields i omitted here for brevity.
Next i want to relate ratings to users and posts. Each post may be rated once by each user, and may be rated by multiple users. I came up with the following relation:
create table posts_ratings_users_map (
postId integer not null references posts (id),
ratingId integer not null references ratings (id),
userId integer not null references users (id),
primary key (postId, ratingId, userId)
);
But here is the problem: i can't see a way to integrate it within Hibernate ORM mapping, to get for each of posts list (or set, or any other collection) of pairs of (User,Rating).
Here is how i am trying to map them now:
User.java:
#Entity(name = "users")
public class User {
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
#OneToMany
#JoinColumn(name = "authorId")
private Set<Post> posts = new HashSet<>();
// ...
// getters and setters
// ...
}
Rating.java:
#Entity(name = "ratings")
public class Rating {
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
#ManyToMany(mappedBy = "ratings")
private Set<Post> posts = new HashSet<>();
// ...
// getters and setters
// ...
}
Post.java:
#Entity(name = "posts")
public class Post {
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String data;
#ManyToOne
#JoinColumn(name = "authorId")
private User author;
#ManyToMany
#JoinTable(
name = "posts_ratings_users_map",
joinColumns = { #JoinColumn(name = "ratingId") },
inverseJoinColumns = { #JoinColumn(name = "postId") }
)
private Set<Rating> ratings = new HashSet<>(); // here is the problem. i can relate ratings to this post, but how
// do i relate them with users which assigned their ratings to this
// post ?
// ...
// getters and setters
// ...
}
What needs to be changed in order to relate list of pairs of rating&user to each post?
UPD1
Obvious error: PK for posts_ratings_users_map should be (postId, userId) (excluding ratingId), otherwise same user was able to put different ratings on the same post.

Maybe change your model a little.
Users have posts which you said is already defined.
Why not create a new Entity 'UserRating'.
#Entity(name = "user_ratings")
public class UserRating {
#Id
#Column(nullable = false)
#GeneratedValue
private Integer id;
#ManyToOne(nullable = false)
#JoinColumn(name = "ratingId")
private Rating rating;
#ManyToOne(nullable = false)
#JoinColumn(name = "authorId")
private User ratedBy;
}
Now on your Post instead of the ManyToMany have a OneToMany relationship. It would be more ideal to use the id of the rating and the user as the key for the UserRating class but this doesnt model easily in JPA/Hibernate which makes it quite complex. If your interested have a look at these questions
Mapping ManyToMany with composite Primary key and Annotation
Many to many hibernate mapping with extra columns?

Include the user in the rating class
#Entity(name = "ratings")
public class Rating {
#Id
#Column(nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
#ManyToMany(mappedBy = "ratings")
private Set<Post> posts = new HashSet<>();
#Onetoone
private User ratedBy;
// ...
// getters and setters
// ...
}

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.

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

list only has one element

I'm using hibernate to read data from a database. I have 3 entities, sensor, zone and a N to N relationship between those two entities, that is the entity SensorZone.
For each entity I created a class, which are the examples that follow, they don't include a constructor, getters and setters:
#Entity
#Table(name = "Sensor")
public class Sensor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "zone")
private List<SensorZone> zones = new ArrayList<>();
}
#Entity
#Table(name = "Zone")
public class Zone {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "max_count")
private int maxCount;
#Column(name = "current_count")
private int currentCount;
#Column(name = "created")
private Timestamp created;
#Column(name = "modified")
private Timestamp modified;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sensor")
private List<SensorZone> sensors = new ArrayList<>();
}
#Entity
#Table(name = "Sensor_Zone")
public class SensorZone {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "zone_id")
private Zone zone;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sensor_id")
private Sensor sensor;
#Column(name = "enter_exit")
private boolean enterExit;
}
I'm using PostgreSQL as a database engine, and the tables are as follow:
CREATE TABLE Sensor (
id SERIAL,
name TEXT NOT NULL UNIQUE,
CONSTRAINT PK_Sensor PRIMARY KEY (id)
);
CREATE TABLE Zone (
id SERIAL,
name TEXT NOT NULL UNIQUE,
max_count INT NOT NULL,
current_count INT NOT NULL,
created TIMESTAMP NOT NULL,
modified TIMESTAMP NOT NULL,
CONSTRAINT PK_Zone PRIMARY KEY (id)
);
CREATE TABLE Sensor_Zone (
id SERIAL,
zone_id INT NOT NULL,
sensor_id INT NOT NULL,
enter_exit BOOLEAN NOT NULL,
CONSTRAINT PK_Zone_Sensor PRIMARY KEY (id, sensor_id, zone_id),
CONSTRAINT FK_Zone_Sensor_Zone FOREIGN KEY (zone_id) REFERENCES Zone (id),
CONSTRAINT FK_Zone_Sensor_Sensor FOREIGN KEY (sensor_id) REFERENCES Sensor (id)
);
And here's the values on the table Sensor_Zone:
The problem is that the field zones from Sensor only has one element in the list, even there are multiple elements in the database.
I've tried to put FecthType.EAGER, but it didn't change anything.
Not the exact solution (a.k.a. Could your model be re-modeled?)
I know it's easier said than done, but if you could avoid managing extra column(s) on the joining table, you could resolve it using standard example of many-to-many with joining table.
Perhaps there is a way of thinking about your extra column so that it could become owned by either Zone or Sensor entities? Maybe it's complementary as in on one side of many-to-many if it is set to TRUE it means one thing, but when it's missing it is equivalent to being FALSE.
#Entity
#Table(name = "Sensor")
public class Sensor {
// ...
#OneToMany
#JoinTable(
name = "Sensor_Zone",
joinColumns = { #JoinColumn(name = "sensor_id") },
inverseJoinColumns = { #JoinColumn(name = "zone_id") }
)
private List<Zone> zones = new ArrayList<>();
}
#Entity
#Table(name = "Zone")
public class Zone {
// ...
#OneToMany
#JoinTable(
name = "Sensor_Zone",
joinColumns = { #JoinColumn(name = "zone_id") },
inverseJoinColumns = { #JoinColumn(name = "sensor_id") }
)
private List<Sensor> sensors = new ArrayList<>();
}
And then your joining table does not need to be an entity simplifying things.
The Solution (Here be dragons)
Escape hatch: for solution please check Mykong's blog
You have to look into using #AssociationOverrides on the entity created from joining table. Please also note a separate class annotated with #Embeddable created to deal with the composite key in the joining table.

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;
}

Categories

Resources