I want to persist my entity with ManyToMany relation. But i have some problem during persisting process.
My entities :
#Entity
#Table(name = "USER")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long userId;
#Column(name = "NAME", unique = true, nullable = false)
String userName;
#Column(name = "FORNAME")
String userForname;
#Column(name = "EMAIL")
String userEmail;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "USER_USER_ROLES", joinColumns = #JoinColumn(name = "ID_USER"), inverseJoinColumns = #JoinColumn(name = "ID_ROLE"))
List<UserRoles> userRoles = new ArrayList<UserRoles>();
// getter et setter
}
and
#Entity
#Table(name = "USER_ROLES")
public class UserRoles implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long userRolesId;
#Column(unique = true, nullable = false, name = "ROLE_NAME")
String roleName;
// getter et setter
}
Service code :
User user = new User();
UserRoles role;
try {
role = userRolesServices.getUserRoleByName("ROLE_USER"); // find jpql - transaction
} catch (RuntimeException e) {
LOGGER.debug("No Roles found");
role = new UserRoles("ROLE_USER"); // create new
}
user.addUserRole(role);
user.setUserName(urlId);
user.setUserForname(fullName);
user.setUserEmail(email);
userServices.createUser(user); // em.persist(user) - transaction
First time, when I try to persist a User with UserRoles "ROLE_USER", no problem. User and UserRoles and join tables are inserted.
My problem is when I try to persist a second User with the same UserRoles.
I check if the UserRoles exists by finding it (userRolesServices.getUserRoleByName(...)).
If exists -> add this UserRoles to User list (id + role name) else i create a new one (only role name).
By when I try to persist the second User, i obtain the following exception :
"detached entity to persist : .....UserRoles" (maybe because getUserRoleByName is performed in another transaction)
If I do not use getUserRoleByName (only *new UserRoles("ROLE_USER");*), i obtain the following exception :
"...ConstraintViolation : Duplicated entry for 'ROLE_NAME' ..."
So, how to properly persist an entity with #ManyToMany relation ?
For above problem I would say your entity relationship cascade is wrong. Consider this: A user can have multiple roles but there can be fixed number of roles that can exist in the system. So CASCADE ALL from User entity does not make any sense, since life cycle of UserRoles should not depend on User entity life cycle. E.g. when we remove User, UserRoles should not get removed.
detached entity to persist exception will only occur when you are passing object which has primary key already set to persist.
Remove cascade and your problem will be solved now only thing you will need to decide is how you are going to insert User roles. According to me there should be separate functionality to do so.
Also do not use ArrayList, use HashSet. ArrayList allows duplicates.
I will provide my answer if anyone get same type of problem to me and the author.
Basically what I was facing was a situation when I had one table which was some kind of CONSTANT values. And the other would change, but it should map (many to many) to those CONSTANTS.
The exact problem is USERS and it's ROLES.
Roles would be known and added on system startup, thus they should never get removed. Even if no user would have some Role it should still be in the system.
The class implementation, using JPA:
User:
#Entity
#Table(name = "USERS")
public class User{
#Id
private String login;
private String name;
private String password;
#ManyToMany(cascade = {CascadeType.MERGE})
private Set<Role> roles = new HashSet<>();
Role:
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#Enumerated(value = EnumType.STRING)
private RoleEnum name;
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>();
Usage
This setup will easily add/remove Role to User. Simply by passing an array, f.e.: user.getRoles().add(new Role("ADMIN")); and merge the user. Removing works with passing an empty list.
If you forget to add the Role before adding it to the user most likely you will get an error like:
javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: com.storage.entities.Role#246de37e.
What and why
mappedBy attribute is added to the child Entity as described in the JPA Docs
If you choose to map the relationship in both directions, then one
direction must be defined as the owner and the other must use the
mappedBy attribute to define its mapping (...)
cascade = {CascadeType.MERGE} is added for proper cascades JPA Docs
Cascaded the EntityManager.merge() operation. If merge() is called on
the parent, then the child will also be merged. This should normally
be used for dependent relationships. Note that this only affects the
cascading of the merge, the relationship reference itself will always
be merged.
(maybe because getUserRoleByName is performed in another transaction)
That would seem to the the issue, do the query in the same transaction/entity manager. Otherwise re-find it in the current transaction using find().
Duplication Reason: Id is autogenerated, so every time a new role is being created.
Use in this way :
User
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_Id;
#Column(name = "email")
private String email;
#Column(name = "firstname")
private String firstname;
#Column(name = "lastname")
private String lastname;
#Column(name = "password")
private String password;
#Column(name = "active")
private int active;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinTable(name="user_role",
joinColumns=#JoinColumn(name="user_Id"),
inverseJoinColumns=#JoinColumn(name="role_Id"))
private Set<Role> roles = new HashSet<>();
//Getter and Setter
Role
#Entity
#Table(name="roles")
public class Role {
#Id
#Column(name="role_Id")
private int role_Id;
#Column(name="role_name")
private String role_name;
#ManyToMany(mappedBy = "roles")
private Set<User> users= new HashSet<>();
Controller (Should have added it to Service)
#PutMapping("/addEmp")
public String addEmp(#RequestBody User user) {
String pass=passencoder.encode(user.getPassword());
user.setPassword(pass);
List<Role> roles =rolerepo.findAll();
for(Role role: roles)
System.out.println("Roles"+ role.getRole_name());
//user.setRoles(new HashSet < > (rolerepo.findAll()));
userrepo.save(user);
return "User Created";
}
Output
Roles
User_Role
If you liked the answer please subscribeYoutube Channel Atquil
I have the same issue, but couldn't get through it yet.
My RelationShip is Hotel to DeliveryPartners.
Following are the classes:
#Entity Class
package com.hotelapp.models;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.Set;
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Hotel {
#Id
#GeneratedValue(generator = "hotel_id", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "hotel_id", sequenceName = "hotel_id")
private Integer hotelId;
private String hotelName;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "address_id")
private Address address;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "hotel_id")
private Set<Menu> menuList;
#ManyToMany(cascade = {CascadeType.MERGE} ,fetch = FetchType.EAGER)
#JoinTable(name ="hotel_delivery", joinColumns = #JoinColumn(name ="hotel_id"), inverseJoinColumns = #JoinColumn(name="delivery_id"))
private Set<Delivery> delivery;
public Hotel(String hotelName, Address address, Set<Menu> menu, Set<Delivery> delivery) {
this.hotelName = hotelName;
this.address = address;
this.menuList = menu;
this.delivery = delivery;
}
#Override
public String toString() {
return "Hotel{" +
"hotelName='" + hotelName + '\'' +
", address=" + address +
", menu=" + menuList +
", delivery=" + delivery +
'}';
}
}
#Delivery Class
package com.hotelapp.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Delivery {
#Id
#GeneratedValue(generator = "del_id", strategy = GenerationType.AUTO)
#SequenceGenerator(name = "del_id", sequenceName = "delivery_id")
private Integer deliveryId;
private String partnersName;
private Double charges;
#ManyToMany(mappedBy = "delivery", cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JsonIgnore
private Set<Hotel> hotelList = new HashSet<>();
public Delivery(String partnersName, Double charges) {
this.partnersName = partnersName;
this.charges = charges;
}
#Override
public String toString() {
return "Delivery{" +
"partnersName='" + partnersName + '\'' +
", charges='" + charges + '\'' +
'}';
}
}
#Controller Class
#PostMapping("/hotels")
public ResponseEntity<Hotel> addHotel(#RequestBody Hotel hotel){
Hotel hotel1 =hotelService.addHotel(hotel);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("desc", "oneHotelAdded");
return ResponseEntity.ok().headers(httpHeaders).body(hotel1);
}
When I use merge cascade type, getting following exception:
Hibernate: insert into address (city, state, street_name, zip_code, address_id) values (?, ?, ?, ?, ?)
Hibernate: insert into hotel (address_id, hotel_name, hotel_id) values (?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into hotel_delivery (hotel_id, delivery_id) values (?, ?)
2020-07-12 00:13:37.973 INFO 50692 --- [nio-9098-exec-1] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2020-07-12 00:13:38.026 ERROR 50692 --- [nio-9098-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - **save the transient instance before flushing: com.hotelapp.models.Delivery; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery] with root cause
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery**
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:347) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:495) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.nullSafeSet(EntityType.java:280) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.writeElement(AbstractCollectionPersister.java:930) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1352) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:52) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_221]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
I realise from the queries part there is no insert query for delivery table so that delivery can be used in hotel_delivery (MTM Table).
Don't know how to proceed now.
I was getting the same error, but after adding the cascade = CascadeType.ALL for both sides of relationship the issue is resolved.
earlier I was cascade = CascadeType.ALL only on the parent side of the relation, after adding child also the code is working fine now.
here is my code.
Reader(parent entity):
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(name="READER_SUBSCRIPTIONS", joinColumns=
{#JoinColumn(referencedColumnName="ID")}
, inverseJoinColumns={#JoinColumn(referencedColumnName="ID")})
private List<Subscription> subscriptions;
Subscription (child entity):
#ManyToMany(mappedBy="subscriptions", cascade = CascadeType.ALL)
private Set<Reader> readers;
Persistence code:
List<Subscription> list = new ArrayList<Subscription>();
list.add(sub1);
list.add(sub2);
Set<Reader> readerSet = new HashSet<Reader>();
readerSet.add(reader1);
readerSet.add(reader2);
reader1.setSubscriptions(list);
reader1.setSubscriptions(list);
sub1.setReaders(readerSet);
sub2.setReaders(readerSet);
reader2.setSubscriptions(list);
reader2.setSubscriptions(list);
readSubscriberRepository.save(reader1);
readSubscriberRepository.save(reader2);
Related
I ran into an error with custom delete method in spring data jpa. Basically there's a bag which contains items, and when deleting the bag, all the items in it should be deleted.
Here're the entities:
#Entity
#Table(name = "bag")
public class Bag {
#Id private Long id;
#Column("uid") private Long uid;
#Column("name") private String name;
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Item> items;
}
#Entity
#Table(name = "item")
public class Item {
#Id private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id")
private Bag bag;
}
and the repository:
#Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
Bag findByUidAndName(Long uid, String name);
#Transactional
#Modifying
#Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
}
When I call bagRepository.deleteByUidAndName(uid, name), I get an Exception from hibernate relating to foreign key constraint. Setting spring.jpa.show-sql=true shows it does not try to delete the items first before deleting the bag.
However, if I call Bag bag = bagRepository.findByUidAndName(uid, name) and then bagRepository.deleteById(bag.getId()) everything is fine.
I'd like to know what's wrong about customizing this delete method and how to fix it.
In case deleting entity via bagRepository.deleteById(bag.getId()) Hibernate will remove from parent to child entity because you defined cascade = CascadeType.ALL on the relation. When we perform some action on the target entity, the same action will be applied to the associated entity.
Logic is in Hibernate and does not utilize database cascades.
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Item> items;
In case bagRepository.deleteByUidAndName(uid, name) you defined native query for deletion. This means that Hibernate logic will be ignored and the query will be executed as-is. You are working directly with the database in this case and to delete record via native SQL you need to define ON DELETE CASCADE on the database level to have similar logic.
#Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
Solution 1, #OnDelete(action = OnDeleteAction.CASCADE)
In case you have auto-generated tables you can add Hibernate-specific annotation #OnDelete to the relation. During tables generation ON DELETE CASCADE will be applied to the foreign key constraint.
Relation definition:
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Item> items;
Auto generated constaint:
alter table item
add constraint FK19sn210fxmx43i8r3icevbeup
foreign key (bid)
references bag
on delete cascade
Implemetation:
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "bag")
public class Bag {
#Id
private Long id;
#Column(name = "uid")
private Long uid;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Item> items;
}
Solution 2, #JoinColumn annotation with foreign key ON DELETE CASCADE
Specify foreign key with ON DELETE CASCADE for Item entity
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id",
foreignKey = #ForeignKey(
name="FK_ITEMS_ID",
foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
private Bag bag;
Implementation:
import javax.persistence.*;
#Entity
#Table(name = "item")
public class Item {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bid", referencedColumnName = "id",
foreignKey = #ForeignKey(
name="FK_ITEMS_ID",
foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
private Bag bag;
}
Solution 3, do not use native query
In this case Hibernate logic will be applied.
Define repository like:
#Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
Bag findByUidAndName(Long uid, String name);
#Transactional
#Modifying
void deleteByUidAndName(#Param("uid") Long uid, #Param("name") String name);
}
Solution 4, Add ON DELETE CASCADE manually to the database
In case your table is not auto-generated you can manually add ON DELETE CASCADE to the database.
alter table item
add constraint FK_BAG_BID
foreign key (bid)
references bag
on delete cascade
I am running into a problem deleting related entities from my database. I have a trading application where users can post trades and express their interests in other people's trades.
When a user deletes their account, all trades posted and interests expressed by this user should be removed from the database. However, the latter doesn't seem to work (I am also not sure if the first one works as I don't know in what order they get executed). I get the error:
The DELETE statement conflicted with the REFERENCE constraint "FKq9kr60l7n7h3yf82s44rkoe4g". The conflict occurred in database "dbi438161_i438161", table "dbo.interests", column 'user_id'.
Note: I get the same when I try to delete a trade but then the column is 'trade_id'
I do the same for the trades and roles of a user so I think it has to do with what is in my interest entity. I am using CascadeType.ALL annotation to let Hibernate remove related entities
Lists of related entities in user:
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Interest> interests = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Trade> trades = new ArrayList<>();
Interest entity:
#Entity
#Table(name = "interests")
public class Interest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int interestId;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne
#JoinColumn(name = "trade_id", nullable = false)
private Trade trade;
private String comment;
public Interest(User user, Trade trade, String comment) {
this.user = user;
this.trade = trade;
this.comment = comment;
}
public Interest(){
}
}
For comparison, the trade entity:
#Entity
#Table(name = "trades")
public class Trade {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="trade_id")
private int tradeId;
#Column(name="wants")
private String wants;
#Column(name="offers")
private String offers;
#Column(name="date_last_modified")
private LocalDateTime lastModified;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="trade")
private List<Interest> interests = new ArrayList<>();
public Trade(String wants, String offers, User user){
this.wants = wants;
this.offers = offers;
this.user = user;
}
public Trade() {
}
}
Does anybody have an idea on what I am doing wrong here? Thanks in advance
Try to set orphanRemoval to true for the following associations:
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Interest> interests = new ArrayList<>();
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Trade> trades = new ArrayList<>();
As it is stated in the documentation:
If the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphanRemoval attribute and dissociating the child will trigger a delete statement on the actual child table row as well.
Please also note that you should not use cascade=CascadeType.ALL for the #ManyToMany association as it explained in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
I have 2 Entities : User.java and PushNotification.java
and mapping table named as userpushnotification where many-to-many mapping takes place.
the existing data in userpushnotification table is a kind of important for me.
So if i try to add users(let's say id=5,6,7) for pushnotification(id=2), hibernate deletes the previous data for pushnotification(id=2) in relationship table and then it adds the new users for that pushnotification(id=2).
My need is to keep all the records in relationship table.
So how can i restrict the Hibernate/JPA to execute only insert queries intstead of executing delete and insert queries.
In simple words, I just want to append data in relationship table instead of overwriting.
User.java :-
#Entity
#Table(name = "user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#GenericGenerator(name = "gen", strategy = "identity")
#GeneratedValue(generator = "gen")
private long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "authkey")
private String authKey;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "users")
private Set<PushNotifications> pushNotifications = new HashSet<PushNotifications>();
PushNotifications.java :-
#Entity
#Table(name = "pushnotifications", uniqueConstraints = #UniqueConstraint(columnNames = { "id" }))
public class PushNotifications implements Serializable {
#Id
#Column(name = "id")
#GenericGenerator(name="gen",strategy="identity")
#GeneratedValue(generator="gen")
private long id;
#Column(name = "shortdescription")
private String shortDescription;
#Column(name = "title")
private String title;
#JsonIgnore
#ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
#JoinTable(name = "userpushnotifications",
joinColumns = {#JoinColumn(name = "pushnotificatoinId") },
inverseJoinColumns = { #JoinColumn(name = "userId") })
private Set<User> users = new HashSet<User>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
When i try to do this :
PushNotifications notifications = iNotificationService
.getNotification(notificationId);
Set<User> newUsers = new HashSet<User>();
newUsers .add(newUserToBeNotified_1);
newUsers .add(newUserToBeNotified_2);
notifications.setUsers(newUsers);
sessionFactory.getCurrentSession().merge(notifications);
Here, am tring to add 2 users for that notification type, Already there is one user for that notification type in relationship table.
Hibernate executing these queries :
Hibernate:
delete
from
userpushnotifications
where
pushnotificatoinId=?
and userId=?
Hibernate:
insert
into
userpushnotifications
(pushnotificatoinId, userId)
values
(?, ?)
Hibernate:
insert
into
userpushnotifications
(pushnotificatoinId, userId)
values
(?, ?)
So, i hope u got me, i dont want hibernate to make delete operations.
Please help me resolve this, Looking for answers...
Thanks in advance.
You could use Blaze-Persistence for this which works on top of JPA and provides support for DML operations for collections. A query could look like the following
criteriaBuilderFactory.insertCollection(entityManager, PushNotifications.class, "users")
.fromIdentifiableValues(User.class, "u", newUsers)
.bind("id", notification.getId())
.bind("users").select("u")
.executeUpdate();
After setting Blaze-Persistence up like described in the documentation you can create a repository like this:
#Component
class MyRepository {
#Autowired CriteriaBuilderFactory cbf;
#Autowired EntityManager em;
public void addUsers(Collection<User> newUsers) {
cbf.insertCollection(em, PushNotifications.class, "users")
.fromIdentifiableValues(User.class, "u", newUsers)
.bind("id", notification.getId())
.bind("users").select("u")
.executeUpdate();
}
}
This will issue just the insert into the join table.
So i'm learning from these simple examples, there're 2 tables, USERS and USER_DETAILS, simple enough, each user has user_details and it's 1-to-1 relationship. So this sample is like this,
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_ID")
private long id;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY)
private UserDetail userDetail;
//Setter and getter methods
}
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_DET_ID")
private long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL")
private String email;
#Column(name = "DBO")
private LocalDate dob;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "USR_ID")
private User user;
//Setter and Getter methods
}
If you look at mappedBy, it's in the User not UserDetails.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
Thanks for your help !
Q2: same examples put mappedBy in the USER_DETAILS side,
why people want to do this ?
How to determine which side to put mappedBy ?
In a bidirectional relationship, each entity has a relationship field
or property that refers to the other entity. Through the relationship
field or property, an entity class’s code can access its related
object. If an entity has a related field, the entity is said to “know”
about its related object.
There is a bidirectional one-to-one relationship in your example. Both User and UserDetail entities have a relationship field. #OneToOne annotation specified on both the entities.
For one-to-one bidirectional relationships, the owning side
corresponds to the side that contains the corresponding foreign key.
The owner of the relationship is UserDetail entity. The owner has #JoinColumn annotation to specify foreign key (USR_ID).
Inverse side of relationship (User) has mappedBy attribute.
Q1: so USER is the owner, if it calls save(),
USER_DETAILS table will be updated as well ?
In your example UserDetail is the owner. Therefore the saving process:
User user = new User(); // Ignoring the constructor parameters...
UserDetail userDetail = new UserDetail();
user.setUserDetail(userDetail);
userDetail.setUser(user);
userRepository.save(user);
You only need to save the parent. It will save the child as well.
I have User and Role classes with ManyToMany relationship. When I'm adding Role object to List roles in User object the 'user_roles' table columns 'username' and 'role_name' gets populated with User and Role objects primary keys' which are their ids. I wanted to know whether its possible to reference not primary keys(ids) and get username and Role.name in those columns instead?
#Entity
#Table(name = "users")
#SecondaryTable(name = "user_info", pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "info_id", referencedColumnName = "user_id") })
public class User {
#Id
#Column(name = "user_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#ManyToMany
#JoinTable(name="user_roles",
joinColumns= {#JoinColumn(name="username"/*, referencedColumnName="username"*/)},
inverseJoinColumns= {#JoinColumn( name="role_name"/*, referencedColumnName="role_name"*/)}
)
private List<Role> roles;
#Entity
#Table(name = "roles")
public class Role {
#Id
#Column(name = "role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "role_name")
private String name;
#ManyToMany
private List<User> users;
Also if I remove the comments around referencedColumnName I get error when trying to fetch all users from the database. I haven't added any data to database so even when querying empty database I'm getting this error:
Caused by: org.hibernate.HibernateException: Found shared references
to a collection: com.recipee.model.User.roles
The reason I'm using such database schema is because I'm trying to use Tomcat realm authentication where I need username and role_name in one database table.