correct way to create relationship in hibernate - java

i am having a mysql table relation with following criteria.
id - auto_increment, primary
a_id - FK to test_table primary key, index present
a_parent_id - FK to test_table primary key, index present
(a_id + a_parent_id) has unique constrain
table entries example
a_id a_parent_id
1 null
2 null
3 1
4 1
5 2
6 5
6 4
currently, i have mapped test_table in hibernate
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Long id;
#Column
private String comment;
what is the correct way to map relation table in hibernate?

As you specify that you can have multiple children/parents and from the looks of the example data, I chose to go with Lists of parents/children instead of just chaining the relationship.
#Entity
#Table(name = "test_table")
public class Test {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Long id;
#Column
private String comment;
#ManyToMany(targetEntity = Test.class)
#JoinTable(name = "relation", joinColumns = {
#JoinColumn(referencedColumnName = "a_id") }, inverseJoinColumns = {
#JoinColumn(referencedColumnName = "a_parent_id") })
private List<Test> parents;
#ManyToMany(targetEntity = Test.class)
#JoinTable(name = "relation", joinColumns = {
#JoinColumn(referencedColumnName = "a_parent_id") }, inverseJoinColumns = {
#JoinColumn(referencedColumnName = "a_id") })
private List<Test> children;
}
To find the parents, you look at the relation table with an SQL like
SELECT a_parent_id FROM relation WHERE a_id = ?
For the children you switch the columns like this:
SELECT a_id FROM relation WHERE a_parent_id = ?
This behavior should be represented by the #JoinTable annotations.

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

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.

How to fetch all many-to-many relations in minimum count of queries?

How can I fetch all many-to-many relations using a minimal number of queries?
I mean without any n+1 queries.
3 - it's normal
I have an entity:
#Entity
#Table(name = "tags")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
}
#Entity
#Table(name = "stations")
public class Station {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
}
#Entity
#Table(name = "songs")
public class Song {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#ManyToMany
#JoinTable(
name = "songs_stations",
joinColumns = {
#JoinColumn(
name = "song_id",
referencedColumnName = "id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "station_id",
referencedColumnName = "id"
)
}
)
private List<Station> stations;
#ManyToMany
#JoinTable(
name = "songs_tags",
joinColumns = {
#JoinColumn(
name = "song_id",
referencedColumnName = "id"
)
},
inverseJoinColumns = {
#JoinColumn(
name = "tag_id",
referencedColumnName = "id"
)
}
)
private List<Tag> tags;
}
And a repository:
public interface SongRepository extends CrudRepository<Song, Long> {
#Query("SELECT s FROM Song s LEFT JOIN FETCH s.tags LEFT JOIN FETCH s.stations")
public List<Song> completeFindAllSongs();
}
so, I can't use eager loading in completeFindAllSongs() cause of cannot simultaneously fetch multiple bags
Can't use #NamedEntityGraph
And I can't load data manually, because I don't have access to songs_tags table
Please don't advise to use #LazyCollection
What should I do?
so, i can't use eager loading in completeFindAllSongs() cause of cannot simultaneously fetch multiple bags
This error should go away if you change your entities to use "Set" instead of "List" for OneToMany and ManyToMany relations.
Edit: So to answer to your question is: You only need 1 Query. Just use Sets and eager fetch whatever you want.
Keep in mind that by join fetching multiple collections you are creating full Cartesian product between the collection rows which may have a huge negative performance impact both on the database and application side.
You may want to consider using batch size to initialize the collections in batches.

JPA #SecondaryTable foreign key violation

I want to map two entities in a one to many fashion.
A->[B, B]
I want to add to the join table more fields. Pojos looks like:
#Entity
#Table(name = "A", schema = "examples")
#SecondaryTable(name = "A_B", pkJoinColumns = #PrimaryKeyJoinColumn(name = "a_id", referencedColumnName = "id"))
public class A
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Basic
private String name;
#Basic
private Integer field1;
#Column(table = "A_B", name = "field2")
private Integer field2;
#OneToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "A_B", joinColumns = {#JoinColumn(name = "a_id")}, inverseJoinColumns = {#JoinColumn(name = "b_id")})
private List<B> datastores;
}
#Entity
#Table(name = "B", schema = "examples")
#SecondaryTable(name = "A_B", pkJoinColumns = #PrimaryKeyJoinColumn(name = "b_id", referencedColumnName = "id"))
public class B
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Basic
private String field1;
#Basic
private int field2;
#Column(table = "A_B", name = "field3")
private int field3;
}
Thing is that in order to add I had to remove the foreign key on A_B table. How do I solve the mapping to allow the foreign keys ?
Thanks.
I am missing something, but I don't see why both Entity A and Entity B are mapping to table "A_B". By adding it to Entity A as a secondary table, you are stating that every time an insert to Table a occurs, an insert to table A_B must also occur - creating a strict 1:1 relation between rows in the two tables. Except that you do the same thing to entity B, so you will end up with rows in A_B with A_id=somevalue, and B_id= null and others with a_id=null while b_id=somevalue. Table "A_B" looks like a relation table, so this probably isn't what you want.
If A_B is a relationtable you should map it using a ManyToMany as you have for the "A_B" table. If there are extra fields that need to be populated, create a AB Entity, and create a OneToMany from A->AB and B->AB, and ManyToOne from AB->A and AB->B.

Categories

Resources