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.
Related
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.
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 have two tables Restaurant and FoodItems.
My requirement:
If I added Restaurant data, then FoodItems data need to be added.
If I deleted Restaurant data, then FoodItems data need to be deleted.
If I updated Restaurant data, then FoodItems data need not to be updated.
Problems faced:
When I updated the restaurant details, then fooditems data is also updated.
Steps to reproduce :
I use postman tool to send JSON data to java side.
JSON :
{
"restaurantId": 1,
"restaurantName": "Salem RRR",
"restaurantAddress": "omr,chennai"
}
I don't pass the foodItems in the JSON. When I directly call session.update(restaurant) in java side,
then fooditems data is deleted.
I know it is because of cascade = CascadeType.ALL option in restaurant table.
My Query :
But I don't know how to prevent the update operation.
I am new to hibernate. please help us to get rid of this problem.
Thanks in advance
Restaurant.java
#Getter
#Setter
#Entity
#Table(name = "restaurant")
public class Restaurant {
#Column(name = "restaurant_id")
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int restaurantId;
#Column(name = "restaurant_name",nullable = false)
private String restaurantName;
#Column(name = "restaurant_address",nullable = false)
private String restaurantAddress;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinTable(name = "restaurant_fooditems",
joinColumns = {#JoinColumn(name="restaurant_id",referencedColumnName = "restaurant_id")},
inverseJoinColumns = {#JoinColumn(name="food_id",referencedColumnName = "food_id")})
private Set<FoodItems> foodItems;
}
FoodItems.java
#Getter
#Setter
#Entity
#Table(name = "fooditems")
public class FoodItems {
#Column(name = "food_id")
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int foodId;
#Column(name = "food_name",nullable = false)
private String productName;
#Column(name = "price",nullable = false)
private double price;
}
I know it is because of cascade = CascadeType.ALL option in restaurant table.
Well, not exactly. It's because Restaurant is the owner of the association between itself and the FoodItems. This means the association state is controlled by the Restaurant.foodItems property.
Assuming that you are simply calling restaurantRepository.save(restaurant) in your service method, then if Restaurant.foodItems is empty, all previous foodItems are assumed to be removed from the association. The problem will persist even without CascadeType.ALL.
The simplest solution would be to load the original Restaurant from the data store, and only copy over the fields you want updated - sth like:
#Transactional
public void update(Restaurant updated) {
restaurantRepository.findById(updated.getId())
.ifPresent(original -> {
original.setRestaurantName(updated.getRestaurantName());
original.setRestaurantAddress(updated.getRestaurantAddress());
});
}
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.
Many times I'm using #Formula in my entities. But always it was a simple query or stored procedure with parameter which I can take as filed from table. Now I need to user some property from related object. But I see exception when try to get object from DB. Please see an example below
#Entity
#Table(name = "MINISTRY")
public class Ministry {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
// unnecessary code
}
#Entity
#Table(name = "DEPARTMENT")
public class Department {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DEP_NAME")
private String departmentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MINISTRY_ID")
private Ministry ministry;
// unnecessary code
}
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPARTMENT_ID")
private Department department;
#Formula("test_package.calc_something(department.ministry.id)")
private BigDecimal someMetric;
// unnecessary code
}
How I should use entity prop in #Formula.
I don't want to write something like
select d.ministry.id from Department d ...
If you read the JavaDoc of Formula you will see:
The formula has to be a valid SQL fragment
So you will have to use SQL like:
#Formula("test_package.calc_something("
+ "select DEP.MINISTRY_ID from DEPARTMENT DEP where DEP.ID = DEPARTMENT_ID"
+ ")")
private BigDecimal someMetric;
The only thing that is modified by Hibernate in the fragment before writing it to SQL: It will add the table alias to your columns (as you can't predict that). I mention that, as only a rudimentary SQL parser is used for that, which will insert the alias at wrong positions for more complex fragments.
A remark about performance: The formula is executed for every Department entity that you load, even if you only want to use the attribute for sorting or filtering (just guessing from the name of the attribute) - unless you use #Basic(fetch = FetchType.LAZY) and turn bytecode instrumentation on (or emulate that with FieldHandled).