How to create a separate table with two mapped fields? - java

I have a user entity. The user will have a list of friends. I have a problem with creating a batch to this mapped list. I want the board to look like this https://zapodaj.net/41cd4553308d3.png.html The first column is the user ID, and the second column is the friend's ID. I was thinking about doing it
#ElementCollection
#CollectionTable(
name = "users_friends",
joinColumns = #JoinColumn(name = "user_id", nullable = false, updatable = false)
)
#Column(name = "user_friend_id")
private Set<UserEntity> friends;
But ElementCollection does not map the entity and this way goes away.
How can I create such a special table for mapping the user with a list of friends?

This is a many-to-many relationship. JPA supports them no problem https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
In your case doing
#ManyToMany
#JoinTable(
name="users_friends",
joinColumns=#JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(name="user_friend_id", referencedColumnName="id"))
private List<UserEntity> friends;
Should do the trick

Related

Spring + Hibernate: How do I efficiently chain two link tables and include resulting data in single entity? (user-role-right)

Short version
I have a basic setup where a user table is linked to a role table and a role table is linked to a right. These are both Many-to-Many relations. The roles are a dynamic entity and not of interest for the application (only for visual aspects). When I fetch a user I want to return the data in the user table including a list of the names of all rights.
To clarify, this is what I want the solution to do:
I managed to get the rights in my user object and return them, but it's inefficient due to the extra query calls hibernate makes after the original query was called.
Detailed version
Let me first give you some information on how to entities are linked and what the code looks like. My (simplified) database table structure looks like this:
User.java
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "user_id", columnDefinition = "user_id")
private Long userId;
#Transient
private List<String> rights
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
Role.java
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "role_id", columnDefinition = "role_id")
private Long roleId;
#ManyToMany
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"))
#JsonIgnore
private List<Employee> employees;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"),
inverseJoinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"))
#JsonIgnore
private List<Right> rights;
//Getters and setters
}
Right.java
#Entity
#Table(name = "right")
public class Right {
#Id
#Column(name = "right_id", columnDefinition = "right_id")
private Long rightId;
#Column(name = "name", columnDefinition = "name")
private String name;
#ManyToMany
#JoinTable(
name = "role_right",
joinColumns = #JoinColumn(name = "right_id", referencedColumnName = "right_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
#JsonIgnore
private List<Role> roles;
//Getters and setters
}
It's important to know that I use the Java Specifications API to join the tables:
return (root, query, cb) -> {
query.distinct(true);
Join rolesJoin = root.join("roles", JoinType.LEFT);
Join rightsJoin = rolesJoin.join("rights", JoinType.LEFT);
return cb.conjunction();
};
This creates the correct query:
select <columns go here>
from employee user0_
left outer join user_role roles1_ on user0_.user_id=roles1_.user_id
left outer join role role2_ on roles1_.role_id=role2_.role_id
left outer join role_right rights3_ on role2_.role_id=rights3_.role_id
left outer join right right4_ on rights3_.right_id=right4_.right_id
Everything looked to good to me till now. But when I tried to fetch the names of all roles, there where more than two queries (count for page and the original one) being executed
//The original code uses lambda for this
for(Role role : user.getRoles()){
for(Right right: role.getRights()){
user.addRight(right.getName());
}
}
The extra query looks like:
select <column stuff>
from role_right rights0_
inner join right right1_ on rights0_.right_id=right1_.right_id
where rights0_.role_id=?
This makes the call very inefficient to me. In this case it's a single user, but with multiple users it adds up.
Is there a way to have a single query put the names of all rights in the user entity, without adding extra query executions?
Things I tried so far:
Using #SecondaryTable to directly define column from the Right table in my User entity. I could not get to first link the Role to the User and then use fields from the Role table to link the Right table. So in the end I would have to #SecondaryTable annotation on top of my User object and define columns of the Right object below.
Using #Formula in the User entity to insert a native call into the query. This did also not work as the annotation did not understand how to map everything into a list of rights.
There might be other options here, or I did something horribly wrong with implementing the ones above. But for now I don't which way to go in finding a solution for my problem. If someone could tell me, that would be great.
Thanks in advance,
Robin
You are using Root.join which does just the joining of tables for the purposes of the query; lazy associations in the loaded entities will still not be initialized.
As I see, your intention is to initialize the lazy collections as well. For that you have to use Root.fetch (defined in the interface method inherited from the FetchParent):
Create a fetch join to the specified collection-valued attribute using
the given join type.
However, your intention is not a good practice; do not join multiple collections in one query, otherwise the query result set will explode with full Cartesian product between the joined collections. Your result set contains <num of users> * <num of roles per user> * <num of rights per role> rows. So, each user data is repeated <num of roles per user> * <num of rights per role> times in the generated result set.
The approach I find to be the best and most straightforward is to specify batch size on lazy associations.

ORA-02292 when using Hibernate

I am trying to implement a delete function but all I get is this ORA-02292 ERROR:
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-02292: Integritäts-Constraint (VDMA.FK892DE8B473F40868) verletzt - untergeordneter Datensatz gefunden
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
I have an Entity (MainEntity) that has an n:m relationship with its child entity.
For example: A User can have multiple cars, each car can be driven by different users.
When a User is deleted, I want the associations between the User and the Car to be deleted as well. That´s why I thought I could do the following :
User Entity
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "user_car", joinColumns = {#JoinColumn(name = DATABASE_COLUMN_ID, nullable = false, updatable = false) }, inverseJoinColumns = {#JoinColumn(name = DATABASE_COLUMN_TYPE_ID,
nullable = true, updatable = false) })
private Set<UserCar> userCars;
Car Entity
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "userCars")
private Set<User> users;
The result is :
The tables get created, the data gets persisted - everything works fine.
EXCEPT deleting entries: As soon as I try to delete a User and the User has a car (and therefore a user : car relationship) I do get the errors shown above.
If the user doesn´t have any cars I can delete him without any issues. So the problem must be the constraint in the USER_CAR table.
Clear userCars as well:
user.getUserCars().clear()
This will break the association between user and associated cars (it will delete the corresponding records from the junction table).
Also, you don't want CascadeType.ALL on many-to-many associations, because it implicitly contains CascadeType.REMOVE. Using it means that removal will be cascaded to cars as well, although there are other users who are associated with those cars.
Pls make sure you delete the child records, adding the orphanRemoval will help
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL,orphanRemoval=true)
#JoinTable(name = "user_car", joinColumns = {#JoinColumn(name = DATABASE_COLUMN_ID, nullable = false, updatable = false) }, inverseJoinColumns = {#JoinColumn(name = DATABASE_COLUMN_TYPE_ID,
nullable = true, updatable = false) })
private Set userCars;
This will delete all cars assoicated with a user.

merge in jpa many-to-many with extra column in join table

I have a many-to-many relationship with three tables and entities adn the join table contains additional column. On both sides of the relationship I have set cascadeType.All
When I add new objects to owner side the merge method works fine but when I remove a child object from the owner and merge it, the corresponding rows in the join table will not be removed and I will have duplicate rows in there.
owner entity
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "definitionType")
private List<DefinitionProperty> definitionProperties = new ArrayList<DefinitionProperty>();
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "property")
private List<DefinitionProperty> definitionProperties= new ArrayList<DefinitionProperty>();
mapping entity
#Id
#JoinColumn(name = "dtid", referencedColumnName = "id")
#ManyToOne(optional = false)
private DefinitionType definitionType;
#Id
#JoinColumn(name = "prid", referencedColumnName = "id")
#ManyToOne(optional = false)
private Property property;
I am not calling remove method of my entity manager at all and I am expecting the cascading to remove the unwanted rows automatically. Is that possible? what should I do to in order to remove those rows?
I can add my code here if it help
It just needed orphanRemoval=true on the owner side.

Unwanted behavior – Hibernate deletes child elements annotated with #ElementCollection when parent element is updated

everyone.
I am have Customer and Service tables in one to many relation - one customer can have no or many services. The Service table has a customer_id column which is a foreign key referencing the primary key of the Customer table.
When retrieving customers from the database I need to get only the IDs of the related services and that is why I decided to use the #ElementCollection annotation.
My mapping for the Customer is as follows:
#Entity
#Table(name = "customer")
public class CustomerEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false)
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "service", joinColumns = #JoinColumn(name = "customer_id", updatable = false, insertable = true))
#Column(name = "id", updatable = false, insertable = true)
private Set<Integer> serviceIds;
}
Everything works perfect when I get data from the database. The problem is when I try to update a customer in the database. Upon update Hibernate deletes all the Service table rows which reference the updated customer if the serviceIds member of the customer entity was set to null or an empty Set. I would like to avoid this behavior. I would like this serviceIds member to be read only for Hibernate and to be ignored when the customer is updated in the database - i.e. I want to update only the customer table and nothing else. Is it possible to achieve this using ElementCollection?
By the way I tried the following mapping and the update of the Customer does not lead to any deletions in the Service table even if I set the serviceIds to null or an empty Set.
#OneToMany
#JoinColumn(name = "customer_id", referencedColumnName = "id", updatable = false, insertable = false)
private Set<ServiceEntity> serviceIds;
Thank You for Your help.
When modifying - even - a single element in an #ElementCollection, Hibernate will delete the old collection (the one persisted in DB) and then insert the new image of the collection. The question was already asked here:
Hibernate - #ElementCollection - Strange delete/insert behavior
In order to avoid this problem you can:
Use a List of Integer with the #OrderColumn annotation as described in the above link.
Create an Entity class wrapping your Integer (+ #Generated #Id).
Best regards

How to generate some JPA entities with a multi join?

i would like to create an application in this context : Zk 6, Spring v3.1.1, JPA 2.0, Hibernate 4.1.4, all with annotations but i have some pb with JPA concept.
Here are a type of case study :
3 tables, all linked via a join table ; we are dealing with cardinality 0, n.
So we have T_E_USER, T_E_TYPE and T_E_AIR.
Each table has a numeric ID, and a simple VARCHAR field.
A join table is created with T_J_USR_TPE_AIR with the 3 ID referenced by foreign keys forming a composed primary key.
I'm using Hibernate Tools for generate my entities (version JPA).
And that's where the problems start ....
I have, in each entity class, an attribute of type set with annotation # OneToMany.
I have a class representing the join that has an id attribute of complex type (another class) with an annotation EmbeddedId for a composite key.
And attributes representing the three entities with annotations # ManyToOne.
Here are my questions, because that's where I'm confused:
which should i set into the "mappedBy" attribute in the annotation # OneToMany of my entities?
Am I forced to do a class entity representing the join?
How does the CASCADE? Is it possible to use it in this context to enrich the join table "automatically"? Or should I manually instantiate the class representative of the join in order to persist the information myself?
A big thank you in advance for any kind soul who could give me a helping hand.
Thank you for your answers but one said "yes" when the other says "no" lol
Here's what I did during the day but I have not yet been tested.
In each entity table, i added a #OneToMany relation with mappedBy setted to the attribute defined in "join" entity :
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "aircraft",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "userAccount",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
...
#OneToMany(fetch = FetchType.LAZY,
mappedBy = "referenceType",
cascade = { CascadeType.REMOVE })
private Set<UserConfig> userConfigs = new HashSet<UserConfig>(0);
And i created a new Entity for the join table.
#Entity
#Table(name = "T_J_USR_RFT_AIR_URA")
public class UserConfig implements java.io.Serializable {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "airId",
column = #Column(name = "URA_AIR_ID", nullable = false)),
#AttributeOverride(name = "usrId",
column = #Column(name = "URA_USR_ID", nullable = false)),
#AttributeOverride(name = "rftId",
column = #Column(name = "URA_RFT_ID", nullable = false))
})
private UserConfigId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_RFT_ID", nullable = false, insertable = false, updatable = false)
private ReferenceType referenceType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_USR_ID", nullable = false, insertable = false, updatable = false)
private UserAccount userAccount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "URA_AIR_ID", nullable = false, insertable = false, updatable = false)
private Aircraft aircraft;
...
getter & setter
}
Where UserConfigId is :
#Embeddable
public class UserConfigId implements java.io.Serializable {
#Column(name = "URA_AIR_ID", nullable = false)
private Integer airId;
#Column(name = "URA_USR_ID", nullable = false)
private Integer usrId;
#Column(name = "URA_RFT_ID", nullable = false)
private Integer rftId;
...
getter & setter
}
What do you think about this practice ?
I just used "cascade" if an object of the join table is deleted in order to delete all element associated in the join.
Is it all right ?
Anyway thank you Tom, i will analyzed your link.
Thank you JMelnyk too.
You are welcome if you want to demonstrate what are the best practices for this case.
Three-way joins are tricky. I think what you've done, using an entity for the join table, is probably the right thing to do. To answer your questions:
Your #OneToMany attributes refer to the entity mapping the join table; they should be mappedBy the appropriate #ManyToOne attribute in that entity.
Yes, unfortunately, an entity for the join table is the best way to do this.
Cascades can be used to automatically add objects to the database, but not to create objects. You will need to create instances of the join entity in code.
which should i set into the "mappedBy" attribute in the annotation #
OneToMany of my entities?
mappedBy attribute represents a property name you are joining on. Read more...
e.g. AnyEntity holds List<Employee> which is joined on (mappedBy) department property in Employee entity, and that department property holds the association.
Am I forced to do a class entity representing the join?
No, you do not provide an entity class for join tables.
How does the CASCADE? Is it possible to use it in this context to
enrich the join table "automatically"? Or should I manually
instantiate the class representative of the join in order to persist
the information myself?
Yes it is possible to enrich associations of the entity and itself by marking associations with desired cascade type.
e.g. We have a Department which holds List<Employee> and I put CascadeType.PERSIST on employees. Now we populate department objects with its properties and employees. When we are finished, we persist only the department, and it will cascade operation to employees.

Categories