I have two entities User and Discount.
User can have many discounts (bonuses, extra points whatever) and a discount can be applied to many users.
With these relational mappings I get three tables: User, Discount and User_Discount.
I create public Discount (for all users), set isActivated = true and save the discount object in every user (not sure if it's good for performance).
My problem is, when I want to deactivate discount for one user -> I get user by id, get discount object and set field isActivated to false and after that operation every user has this discount field set to false. So it's one shared object for every user. I want activate/deactivate separately for users. How to resolve that? In what structure should I keep this flag activated/deactivated?
User_Discount table I actually need to get info if a specific discount is assigned to any user and if I can delete it. Maybe I don't need this mapping?
#Data
#Entity
public class Discount {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private boolean isActivated;
private BigDecimal value;
private String discount_group;
#ManyToMany
#JoinTable(name = "user_discount", joinColumns = #JoinColumn(name = "discount_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
#JsonIgnore
private Set<User> users = new HashSet<>();
}
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long user_id;
private String firstName;
private String lastName;
#ManyToMany(mappedBy = "users")
private Set<Discount> discounts = new HashSet<>();
}
You need to create an entity class for your User_Discount table and add the additional fields you want to it. Get a User_Discount entity by user and change the flag in it.
check out this article.
Related
I have a one to many relation on post class, and on the relation table I have one to one relation with user. Everything works find, but i want to be able to remove the relation, keeping the user entity, is that possible?
At this moment with the annotation orphanRemoval = true when I remove from post Detail list an element, this its removed from post_details table but the user is removed too.
#Entity
#Table(name = "ta_post")
public class Post{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private Date fcDate;
#OneToMany(mappedBy="post", cascade=CascadeType.ALL, orphanRemoval = true)
private List<PostDetails>;
}
#Entity
#Table(name = "ta_user")
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int mail;
}
#Entity
#Table(name = "ta_post_details")
public class PostDetails{
private int id;
#ManyToOne
#JoinColumn(name="post_id")
private Post post;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="user_id")
private User user;
private String postComments;
}
You must remove the CascadeType.ALL from the PostDetails. If you want to be able to change the User through the PostDetails, you can set the CascadeType to PERSIST or MERGE. If you want to create a PostDetail along with an User, you need to include the CascadeType CREATE.
I'd guess you are creating the user somewhere else and you just associate one with a Post, so removing the CascadeType.ALL should be enough to not delete your User from the database.
I was trying to do #ManyToMany association and it worked when I tried to do relations like
User can have multiple group and one group can multiple user.. it worked ,and hibernate created custom table based on it automatically and it did its worked. later I had to add more columns to the association table so I followed a article and set the things up as per that, which worked pretty good.
I am using SpringBoot and is using SpringDataJPA
Here is my implementation :
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue
private long id;
private String username;
private String password;
private String email;
#OneToMany(mappedBy = "user")
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "group")
private Set<UserGroup> userGroups = new HashSet<UserGroup>();
}
#Entity
#Table(name = "USERS_GROUPS")
public class UserGroup {
#Id
#GeneratedValue
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GROUP_ID")
private Group group;
// additional fields
private boolean activated;
private Date registeredDate;
}
User user = new User("tommy", "ymmot", "tommy#gmail.com");
Group group = new Group("Coders");
User persistedUser = userRepository.save(user);
Group persistedGroup = groupRepositry.save(group);
UserGroup userGroup = new UserGroup();
userGroup.setGroup(persistedGroup);
userGroup.setUser(persistedUser);
userGroup.setActivated(true);
userGroup.setRegisteredDate(new Date());
userGroupRepository.save(userGroup);
Now how to write a SpringData equavalent methd name for getting user's
group where the user is active ? i.e I make user active = false when
some one deletes users from a group instead of deleting the entry from
the user_group assossiation table.
Can we do it on the userRepository?
I think that you would like to have repository similar to this one:
public interface UserGroupRepository extends JpaRepository<UserGroup, Long> {
List<UserGroup> findAllByUserAndActivatedIsTrue(User user);
}
This method will give you List of all groups that this user is assigned to and is active.
If you would like to parameterize also activated field, you should instead use
List<UserGroup> findAllByUserAndActivated(User user, boolean activated);
I hope that this helps you. Good luck.
And btw, I recommend reading this:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
Helps a lot
I'm creating rating system for simple web application that allows users to post text, similiar to twitter's wall. I've got two entities:
First one:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String login;
private String hashPassword;
private String name;
private String surname;
#Column(unique = true)
private String email;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "connectedUser", fetch = FetchType.EAGER)
private List<Post> userPosts = new ArrayList<>();
Second one:
#Entity
#Table(name = "post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String text;
#NotNull
private LocalDateTime postDate;
#ManyToOne(optional = false)
#JoinColumn(name = "user_id")
private User connectedUser;
And I'm trying to figure out, where/how to add something responsible for rating. Every single post can be rated once per user (Plus or minus) and total sum of rate should be displayed nearby. It seems simple, I need separate table in database with user_id, post_id, and rate, but how could I do that in Hibernate (Hibernate creates database by itself)? Is there any simple solution for this?
If you need additional table - you need additional Entity.
For storing the user actions related to post:
#Entity
#Table(name = "user_post_rate")
public class UserPostRate {
#OneToMany
private Post post;
#OneToOne
private User user;
private boolean upvote;
// ...
}
It could be just boolean value if you have two fixed actions related to the post. You can replace it with some integer values, let's say, for example if privileged user can upvode it for + n, or user can upvote it again after some time and etc.
However you still need sum of rated values to be stored somewhere (not to calculate it time after time).
The overall post score is not a good place to be stored in the same table when user-post related actions are stored, because you will keep many unnecessary duplicates here (until you'll need to keep track of post score history). You can store it in Post entity, because the score of the post is part of its state:
#Entity
#Table(name = "post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// skipped
private long score;
}
Every time a user will rate the post, update for the post entity score should be triggered.
I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.
I have an entity called User with these fields :
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "last_name")
private String lastName;
#OneToMany(mappedBy="userId")
private List<Survey> survey= new ArrayList<>();
And the Survey entity which has :
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SURVEY_ID")
private Long Id;
#ManyToOne
#JoinColumn(name = "user_id",referencedColumnName="user_id")
private User userId;
.....
I want one User to be have many Surveys, and each survey is related to one user.
But there is something wrong with the way I've mapped it, cuz as JSON file, when I access allUsers I get this :
[{"id":1,"name":"User","lastName":"user","email":"user#user.com","surveyData":[{"userId":{"id":1,"name":"User","lastName":"user","email":"user#user.com","surveyData": ,...... and repeats itself
So instead of getting as list the values of the survey data, I get the values of the Users information ?
Can someone help me with this ?
Your mapping is correct.
Just use #JsonManagedReference in your User class and #JsonBackReference in your Survey Class. #JsonManagedReference is the forward part of reference – the one that gets serialized normally. #JsonBackReference is the back part of reference – it will be omitted from serialization.
In the User Class:
#OneToMany(mappedBy="userId")
#JsonManagedReference
private List<Survey> survey;
In the Survey Class:
#ManyToOne
#JoinColumn(name = "user_id",referencedColumnName="user_id")
#JsonBackReference
private User userId;
I have 2 remarks:
If the surveys are not ordered, you can consider to use a Set instead of a List.
I would also recommend to rename the class variable userId in the Survey class to user, since it is a User object and no identifier.