I have a class Group and a class User
where Group and User are many-to-many relation ship
If i change the groups of a user and save a user i want to update the groups
and vice versa where if i changed a user of a group and save the group i want the user to be updated
Do i have to set the mappedBy in both classes
Note : I am using eclipseLink
For a many-to-many relationship, you will need to update the relationship data on both sides in order to stay consistent.
Unfortunately there is no shortcut.
What you can do is to create a single method on either of the entities or a third class to encapsulate the consistent update.
Beware of infinite loops - do not implement the propagation to the other entity in both entity classes.
Roughly like this:
public class User
...
public void addGroup(Group g){
groups.add(g);
g.addUser(this);
}
or
public class Group
...
public void addUser(User u){
users.add(u);
u.addGroup(this);
}
I assume the presence of proper cascade settings on the relationship annotations.
There is a difference between owning a relation and a bidirectional reference. The former primarily concerns the layout of your database where the latter concerns your application logic. From your question, I assume that you want the latter. At the same time, it is generally recommendable that only one side of a relation owns a reference. You can easily create a bidirectional reference while keeping a clear collection owner by creating add and remove methods that enforce bidrection:
class Group {
#ManyToMany
private Collection<User> users = ... ;
public void addUser(User user) {
if(user != null && !users.contains(user)) {
users.add(user)
user.addGroup(this);
}
}
public void removeUser(User user) {
if(user != null && users.contains(user)) {
users.remove(user)
user.removeGroup(this);
}
}
}
class User {
#ManyToMany(mappedBy="users")
private Collection<Group> groups = ... ;
public void addGroup(Group group) {
if(group != null && !groups.contains(group)) {
groups.add(group)
group.addUser(this);
}
}
public void removeGroup(Group group) {
if(group != null && groups.contains(group)) {
groups.remove(group)
group.removeUser(this);
}
}
}
In this example, Group owns the relation what does however not affect the application logic. Be aware of the manipulation order in order to avoid infinite loops. Also, note that this code is not thread-safe.
I understand that for author it is a little bit late, but maybe it will be helpful for another readers.
You can achieve this by adding #JoinTable for both sides:
(cascading you can add by your needs)
public class Group {
....
#ManyToMany
#JoinTable(
name = "group_user",
joinColumns = #JoinColumn(name = "groups_id"),
inverseJoinColumns = #JoinColumn(name = "users_id"))
private List<User> users;
}
public class User {
....
#ManyToMany
#JoinTable(
name = "group_user",
joinColumns = #JoinColumn(name = "users_id"),
inverseJoinColumns = #JoinColumn(name = "groups_id"))
private List<Group> groups;
}
Related
In my app, I have a many-to-many association between the User and Preference entities. Since the join table requires an additional column, I had to break it down into 2 one-to-many associations as such:
User entity :
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade={CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
public Set<UserPreference> getPreferences()
{
return preferences;
}
Preference entity :
#OneToMany(mappedBy = "preference", fetch = FetchType.EAGER)
public Set<UserPreference> getUserPreferences()
{
return userPreferences;
}
UserPreference entity :
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
public User getUser()
{
return user;
}
public void setUser(User user)
{
this.user = user;
}
#ManyToOne
#JoinColumn(name = "preference_id", nullable = false)
public Preference getPreference()
{
return preference;
}
public void setPreference(Preference preference)
{
this.preference = preference;
}
#Column(nullable = false, length = 25)
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
To update one of the preferences, I loop through the user's set of preferences and update the value as such:
#RequestMapping(value = {"/edit-{id}-preference"}, method = RequestMethod.POST)
public String updateUserPreference(#ModelAttribute("userPreference") UserPreference preference, BindingResult result, ModelMap model)
{
User loggedInUser = (User)session.getAttribute("loggedInUser");
for (UserPreference pref : loggedInUser.getPreferences())
{
if (Objects.equals(pref.getId(), preference.getId()))
{
pref.setValue(preference.getValue());
}
}
userService.update(loggedInUser);
return "redirect:/users/preferences";
}
I have confirmed that the user variable I'm trying to update does indeed contain the new value after this code runs. Even weirder, the value does update on the webpage when the redirect happens but the database does NOT update! This is the code I'm using to do the update, this class is annotated with #Transactional and every other call to this method (to update the user's role for example) works perfectly:
#Override
public void update(User user)
{
User entity = dao.findById(user.getId());
if (entity != null)
{
entity.setUserId(user.getUserId());
entity.setPassword(user.getPassword());
entity.setFirstName(user.getFirstName());
entity.setLastName(user.getLastName());
entity.setRole(user.getRole());
entity.setPreferences(user.getPreferences());
}
}
This acts like hibernate's session "cache" has the updated value but does not actually persist it. I am using this very same update method style for about 30 other entities and everything works fine. This is my only many-to-many association that I had to break down into 2 one-to-many associations so I have nothing to compare to.
Am I doing something wrong? When I create a user with a new HashSet and persist it, the value is written correctly in the "join table".
*****EDIT*****
For comparison, this is the code I use to create a new user with default preferences. The preferences exist already but the join table is completely empty and this code correctly persists the entities:
User user = new User();
user.setUserId("admin");
user.setPassword(crypter.encrypt("admin"));
user.setFirstName("admin");
user.setLastName("admin");
user.setRole(roleService.findByName("Admin"));
Set<UserPreference> userPreferences = new HashSet<>();
Preference preference = preferenceService.findByName("anchorPage");
UserPreference userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("System Statistics");
userPreferences.add(userPreference);
preference = preferenceService.findByName("showOnlyActivePatients");
userPreference = new UserPreference();
userPreference.setUser(user);
userPreference.setPreference(preference);
userPreference.setValue("true");
userPreferences.add(userPreference);
user.setPreferences(userPreferences);
userService.save(user);
Thanks
Instead of
entity.setPreferences(user.getPreferences());
Do something like:
for( UserPreference uf : user.getPreferences() ) {
entity.getPreferences().add( uf );
}
The main difference here is that you aren't changing the list reference, which is managed by Hibernate, and is only adding elements to it.
How about using merge? That is what you are cascading after all and you have modified a detached object and need to merge back the changes:
public void update(User user) {
dao.merge(user);
}
EDIT: for clarity this replaces the old update method, so it should be called from the client side with loggedInUser, just like before.
EDIT 2: as noted in the comments merge will update all fields. The old update method also seems to do that? Optimistic locks (version numbers) can be used to guard against overwriting other changes by mistake.
There will be many users and many stores. User can review, make favorite, can rate the store. I have to design entities for this requirement.
I have created User Entity and Review Entity and Store Entity.
Entity design to make store favorite is briefly explained below
#Entity
#Table(name = "favourite")
public class FavouriteEntity{
#ManyToOne
#JoinColumn(name = "user_id", nullable = true)
private UserEntity userEntity;
#Type(type="true_false")
private boolean value;
#ManyToOne
#JoinColumn(name = "accessory_id", nullable = true)
private StoreEntity storeEntity;
public FavouriteEntity(UserEntity user, boolean value, StoreEntity storeEntity) {
this.value = value;
this.storeEntity = accessoryEntity;
this.userEntity = user;
}
}
#Entity
#Table(name = "store")
public class StoreEntity {
#OneToMany(cascade = CascadeType.ALL)
private List<FavouriteEntity> favouriteEntities;
-----------------------------
}
Method to make store favorite is below.
public void makeStorefavorite(long storeId, boolean val, long userId) {
StoreEntity accessoryEntity = storeRepository.findOne(storeId);
UserEntity userEntity = userRepository.findOne(userId);
FavouriteEntity fEntity = favouriteRepository.findAccessoryFavourite(storeId, userId);
if (fEntity == null) {
fEntity = new FavouriteEntity(userEntity, val, storeEntity);
} else {
fEntity.setValue(val);
}
storeEntity.getFavouriteEntities().add(fEntity);
storeRepository.save(storeEntity);
}
Is it a good design? and when user wants to see all the stores with favorite details, In order to solve this with the current approach , I have to first read all the stores, In each store there will be List of favorite entities, next I have to check for user id among those favorite entities to see user's favorite store or not.
I can solve this issue using favouriteRepository.findAccessoryFavourite(storeId, userId); for every storeId I should make a call to DB to get favoriteEntity. from that I can find user made this store favorite or not.
But I would like to know, what is the best approach to solve this?
I have to handle reviews and ratings also for store.
( I dont have enough credits to comment, so I will post this as answer )
You can have this schema.
Consider 4 Entities: UserEntity, StoreEntity, FavouriteEntity, ReviewEntity
UserEntity to FavouriteEntity ---> One to Many (to access all favourites without bothering stores)
UserEntity to ReviewEntity ---> One to Many
ReviewEntity to StoreEntity ---> Many to One ( to access all reviews of a store without bothering user)
As Matt mentioned, don't append 'Entity' too much. Call them User, Store, Favourite and Review.
I would like to get some advice with a question that might make no-sense or may be it does. Let's have a profile object that has a set of Interest with a Many2Many relationship like this one:
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="profile_interests",
joinColumns={ #JoinColumn(name="profile_id") },
inverseJoinColumns = { #JoinColumn(name="interest_id") } )
#OrderColumn(name="display_order")
private Set<Interest> interests;
//GETTER AND SETTERS
public Set<Interest> getInterests() {
return interests;
}
public void setInterests(Set<Interest> interests) {
this.interests = interests;
}
public void addInterest(Interest interest) {
interests.add(interest);
}
public void removeInterest(String interestName) {
interests.remove(new Interest(interestName));
}
In my application controller I can add and delete interests in this way.
#RequestMapping(value="/save-interest", method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> saveInterest(#RequestParam("name") String interestName) {
SiteUser user = getUser();
Profile profile = profileService.getUserProfile(user);
String cleanedInterestName = htmlPolicy.sanitize(interestName);
Interest interest = interestService.createIfNotExists(cleanedInterestName);
profile.addInterest(interest);
profileService.save(profile);
return new ResponseEntity<>(null, HttpStatus.OK);
}
#RequestMapping(value="/delete-interest", method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> deleteInterest(#RequestParam("name") String interestName) {
SiteUser user = getUser();
Profile profile = profileService.getUserProfile(user);
profile.removeInterest(interestName);
profileService.save(profile);
return new ResponseEntity<>(null, HttpStatus.OK);
}
Eventually, a profile, a profile_interests and an interests table will be created. The profile_interest table will have a profile_id and an interest_id, right?
Now imagine that I also want to have other Sets of let's say: activities, passions OR hates, dislikes, tasks, vocations. I can repeat these same process again and again to cover the 6 new (activities, passions, hates, dislikes, task, vocation).
At some point one person may have and interest in Cars, whether other has a passion in Cars, a third one hates Cars and a fourth one say Cars are his Vocation.
If I create 7 different Sets of objects (interests, activities, passions, hates, dislikes, task, vocation) I will repeat many of them in all the tables.
-Is there any way to have a common (interests, activities, passions, hates, dislikes, task, vocation) table for the 7 set of objects, but 7 different intermediate tables (profile_interests, profile_activities, profile_passions, profile_hates, profile_dislikes, profile_task, profile_vocation) using the common table?
Thanks. I appreciate your help with a non-programmer. May be it is a problem well documented and already solved, I dont know.
PD: The Interest entity is here:
#Entity
#Table(name = "interests")
public class Interest implements Comparable<Interest> {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "interest_name", unique = true, length = 25)
private String name;
public Interest() {
}
In JPA 2 entities can be related in more than one ways - and it is perfectly legal. So, just like the interests, the (say) activities would be mapped in the Profile entity as:
#ManyToMany(fetch=FetchType.LAZY) // don't use EAGER unless you really want to :)
#JoinTable(name="profile_activities",
joinColumns={ #JoinColumn(name="profile_id") },
inverseJoinColumns = { #JoinColumn(name="interest_id") } )
#OrderColumn(name="display_order")
private Set<Interest> activities;
//GETTER AND SETTERS AS FOR interests
You haven't shown the Interest entity. If the relations are bidirectional, the Interest would have to have many different Set<Profile> fields, one for each relation it participates (interests, activities, ...). In that case the mappedBy attribute of the field in the Interest entity must point to the appropriate field of the Profile.
This also assumes that business-wise all the relations are between the same entities. A side-effect would be that the list where the user must pick an activity is the same as the list where the user must pick an "interest". If that it not exactly so, then you may have to do more.
I'm trying to implement Many-to-many relation using Hibernate and MySQL DB.
I have class User:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "users_nodes",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "node_id")})
private List<Node> nodeList;
and class Node:
#ManyToMany(mappedBy = "nodeList", cascade = CascadeType.ALL)
private List<User> users;
When I'm trying to save a new Node to DB(which already have user in it) it successfully add new entity to "nodes" table, but relation table "users_nodes" is empty.
This is the way I save Node entity:
#Override #Transactional
public void persist(Node entity) {
getCurrentSession().save(entity);
}
Thanks for your help!
You have to update the owner side of the association (User.nodeList), meaning that you have to associate the User with the Node for each associated user. For example:
class Node {
...
public void addUser(User user) {
if (!users.contains(user)) {
users.add(user);
user.addNode(this);
}
}
}
class User {
...
public void addNode(Node node) {
if (!nodeList.contains(node)) {
nodeList.add(node);
node.addUser(this);
}
}
}
If this would be a performance issue (if Users have many Nodes so it would be expensive to load them all when associating a new node with the desired users), you could change your mappings so that Node is the owner of the association and/or you could consider other options and improvements described here.
I am trying to implement my model using hibernate annotations. I have 3 classes, image, person, and tags. Tags is a a table consisting of 4 fields, an id, personId, imageId, and a createdDate. Person has the fields name, id, birthdate, etc. My image class is defined as follows:
#Entity
#Table(name="Image")
public class Image {
private Integer imageId;
private Set<Person> persons = new HashSet<Person>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Integer getImageId() {
return imageId;
}
public void setImageId(Integer imageId) {
this.imageId = imageId;
}
#ManyToMany
#JoinTable(name="Tags",
joinColumns = {#JoinColumn(name="imageId", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="personId", nullable=false)})
public Set<Person> getPersons() {
return persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
If I remove the annotations on the getPersons() method I can use the classes and add and remove records. I want to fetch all the tags with the image and I am trying to use a set. I keep getting the following error:
org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.exmaple.persons, no session or session was closed
Can someone please help me and let me know what I am doing wrong?
Thank you
This error message - which actually has nothing to do with your association mapping strategy or annotations - means that you have attempted to access a lazy-loaded collection on one of your domain objects after the Session was closed.
The solution is to either disable lazy-loading for this collection, explicitly load the collection before the Session is closed (for example, by calling foo.getBars().size()), or making sure that the Session stays open until it is no longer needed.
If you are not sure what lazy-loading is, here is the section in the Hibernate manual.
Thanks for the response matt. I am confused now. My query to retrieve the image looks like this:
public Image findByImageId(Integer imageId) {
#SuppressWarnings("unchecked")
List<Image> images = hibernateTemplate.find(
"from Image where imageId=?", imageId);
return (Image)images.get(0);
}
I thought that I can call the single hql query and if my mappings are correct it will bring back the associated data.
I was looking at this example at this link hibernate mappings:
2.2.5.3.1.3. Unidirectional with join table
A unidirectional one to many with join table is much preferred. This association is described through an #JoinTable.
#Entity
public class Trainer {
#OneToMany
#JoinTable(
name="TrainedMonkeys",
joinColumns = #JoinColumn( name="trainer_id"),
inverseJoinColumns = #JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
#Entity
public class Monkey {
... //no bidir
} Trainer describes a unidirectional relationship with Monkey using the join table TrainedMonkeys, with a foreign key trainer_id to Trainer (joinColumns) and a foreign key monkey_id to Monkey (inversejoinColumns).