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.
Related
I have an issue when trying to update the contents of a cart with new values added one by one. I am using Spring boot with Hibernate, JPA Repositories, MySQL Database and a front-end built with vanilla JS.I will describe my issue in the following lines.
I am having an user entity that looks like this:
#Entity
#Table(name = "users")
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#Column(name = "cartprod_id")
#OneToMany(cascade = {CascadeType.ALL})
private List<CartItem> cartProduct;
This entity has a List<CartItem> field that looks like this:
#Entity
#Getter
#Setter
public class CartItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "product_id")
private int productId;
#JsonIgnore
#ManyToOne
private User user;
}
And the relationship between them in the Database looks like in this image:
The idea of the above code is to have a way to keep the state of the products Cart of an user.
The flow is as follows :
I am doing a request from the front-end each time a user adds a product in the cart, when this happens, I am saving the contents of the cart(that consist of only the products ID's) in the database using the CartItem entity.
The problem
With this approach, instead of saving only the last product added to the cart(or replace the cart completely with the new values similar to an update), it is inserting the same object over and over again but with all the new appended values instead of overwriting the old object(table) in the database. An example of this would be in this first image . As you can see I have added a product to the cart with id 327 first.
Next I am adding a product with id 328 but it also adds 327 a second time, this goes on and on, the more products I add. This last code snippet contains my controller code .
#PostMapping("/savecart")
public ResponseEntity<String> saveCartToDb(#RequestBody List<CartItem> cartItemList, Principal principal){
System.out.println(cartItemList);
User logedInUser = userService.findUserByUsername(principal.getName()).get();
List<CartItem> cartItem = logedInUser.getCartProduct();
if(cartItem.isEmpty()){
logedInUser.setCartProduct(cartItemList);
userService.saveNewUser(logedInUser);
}else {
cartItem = cartItemList;
logedInUser.setCartProduct(cartItem);
userService.saveNewUser(logedInUser);
}
// userService.saveNewUser(logedInUser);
return ResponseEntity.ok().body("ASD");
}
How can I overwrite the contents of the List<CartItems> for the user so that I will not append new and old values again and again ? Would a SET help as it won't allow duplicates ?
I have also tried this How do I update an entity using spring-data-jpa? but I a not sure that I need to create a #Query for this issue.
I managed to make it work. The solution is the following.
This is a bidirectional one-to-many / many-to-one issue. In order to be able to remove child elements from the parent, we need to decouple(detach) them from the parent. Since parent and child are bound together by a foreign key the detachment has to be done on both ends. If one has a reference to the other this will not work so BOTH REFERENCES have to be removed. 2 methods are needed and both need to be called when doing decoupling. This is what I used.
private Set<CartProduct> cartProduct;
This one to be added in the parent class (User).
public void removeChild(CartProduct child) {
this.cartProduct.remove(child);
}
This one to be added in the Child class
public void removeParent() {
this.user.removeChild(this);
this.user = null;
}
Methods also have to be called like this
for(CartProduct cartItem : cartItemList){
cartItem.removeParent();
logedInUser.removeChild(cartItem);
}
L.E
It may be that with the above implementation you will get a
java.util.ConcurrentModificationException: null
it happen to me in one of the cases too. In order to fix this I used an Iterator like below.
for (Iterator<CartProduct> iterator = cartItemList.iterator(); iterator.hasNext();) {
CartProduct cartItem = iterator.next();
if (cartItem != null) {
iterator.remove();
}
}
I am developing a mall system where a User can have one or more shops. If you create a shop, you have the role ADMIN for that shop, else: you create an account then you are assigned a shop as a MANAGER by the ADMIN of that shop. A user can have a shop that they are an admin, but as well be a manager to a different shop assigned to you by an owner of a that shop. Thus, i have come up with three entities: User, Role and Shop.
User Entity
#Entity
#Table(name = "us_users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotBlank
private String uuid;
#Email
private String us_email;
//other fields
//getters/setters
}
Role Entity.
#Entity
#Table(name = "ro_roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String rn_role_name;
//setter getter
}
Shop Entity.
#Entity
#Table(name = "sh_shops")
public class Shop {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#NotBlank(message = "Shop name can not be left blank")
private String sh_name;
#NotBlank(message = "please provide shop icon")
private String sh_icon;
#NotBlank(message = "please fill the shop description")
private String sh_description;
//other fields.. getters and setters
}
I need a relationship between these three entities: something like a table having userId,shopId,RoleId in which a user can
Log in the system and the system is able to determine the roles under this user and for which shop.(Using Spring security GrantedAuthorities).
Show the user shops under his account and the user can only operate a shop with correct role.
Kindly assist on how to model this use case. Thank you
First, do you really need flexibility with the roles? Maybe having a role as a simple enum would be enough, at least if you don't plan on creating new roles at runtime. This would simplify the data model.
Second, this sounds like a map relationship for the user entity:
#Entity
#Table(name = "us_users")
public class User {
#ManyToMany
#MapKeyJoinColumn(name = "shop_id")
#JoinTable(name = "user_shop_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Map<Shop, Role> shopRoles = new HashMap<>();
}
Here, you create a three-way relationship using a user_shop_role table containing fields user_id, shop_id and role_id, meaning what you'd expect.
Then adding a shop role for the user is just a matter of adding a key-value relationship to this map:
user.getShopRoles().put(shop, role);
Showing the list of shops is just iterating through the map entries and showing a shop (key) with a corresponding role (value).
Since you use Shop as a key, you need to make sure that the Shop entity implements equals and hashCode methods correctly (based on its id).
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 am writing a Spring Boot application that will use Hibernate/JPA to persist between the app and a MySQL DB.
Here we have the following JPA entities:
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#Type(type="uuid-binary")
private UUID refId;
}
#Entity(name = "contacts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="contact_id")),
#AttributeOverride(name = "refId", column=#Column(name="contact_ref_id"))
})
public class Contact extends BaseEntity {
#Column(name = "contact_given_name")
private String givenName;
#Column(name = "contact_surname")
private String surname;
#Column(name = "contact_phone_number")
private String phone;
}
#Entity(name = "assets")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="asset_id")),
#AttributeOverride(name = "refId", column=#Column(name="asset_ref_id"))
})
public class Asset extends BaseEntity {
#Column(name = "asset_location")
private String location;
}
#Entity(name = "accounts")
#AttributeOverrides({
#AttributeOverride(name = "id", column=#Column(name="account_id")),
#AttributeOverride(name = "refId", column=#Column(name="account_ref_id"))
})
public class Account extends BaseEntity {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
private Contact contact;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "asset_id", referencedColumnName = "asset_id")
private Asset asset;
#Column(name = "account_code")
private String code;
}
And the #RestController, where an Account instance will be POSTed (to be created):
public interface AccountRepository extends CrudRepository<Account, Long> {
#Query("FROM accounts where account_code = :accountCode")
public Account findByCode(#Param("accountCode") String accountCode);
}
#RestController
#RequestMapping(value = "/accounts")
public class AccountController {
#Autowired
private AccountRepository accountRepository;
#RequestMapping(method = RequestMethod.POST)
public void createNewAccount(#RequestBody Account account) {
// Do some stuff maybe
accountRepository.save(account);
}
}
So the idea here is that "Account JSON" will be sent to this controller where it will be deserialized into an Account instance and (somehow) persisted to the backing MySQL. My concern is this: Account is a composition (via foreign keys) of several other entities. Do I need to:
Either create CrudRepository impls for each of these entities, and then orchestrate save(...) calls to those repositories such that the "inner-entitities" get saved first before the "outer" Account entity?; or
Do I just save the Account entity (via AccountRepository.save(account)) and Hibernate/JPA automagically takes care of creating all the inner/dependendent entities for me?
What would the code/solution look like in either scenario? And how do we specify values for BaseEntity#id when it is an auto-incrementing PK in the DB?
That depends on your design and specific use cases, and what level of flexibility you want to keep. Both ways are used in practice.
In most CRUD situations, you would rather save the account and let Hibernate save the entire graph (the second option). Here you usually have another case which you didn't mention, and it is updating of the graph, which you would probably do the same way, and actually the Spring's repository save method does it: if the entity is a new (transient) one, it persists it, otherwise it merges it.
All you need to do is to tell Hibernate to cascade the desired entity lifecycle operations from the Account to the related entities:
#Entity
...
public class Account extends ... {
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Contact contact;
#OneToOne(..., cascade = {CascadeType.PERSIST, CascadeType.MERGE})
...
private Asset asset;
...
}
However, you pay the penalty of reloading the object graph from the db in case of merge operation, but if you want everything done automatically, Hibernate has no other way to check what has actually changed, other than comparing it with the current state in the db.
Cascade operations are applied always, so if you want more flexibility, you obviously have to take care of things manually. In that case, you would omit cascade options (which is your current code), and save and update the parts of the object graph manually in the order that does not break any integrity constraints.
While involving some boilerplate code, manual approach gives you flexibility in more complex or performance-demanding situations, like when you don't want to load or reinitialize the parts of the detached graph for which you know that they are not changed in some context in which you save it.
For example, let's assume a case where there are separate web service methods for updating account, contact and asset. In the case of the account method, with cascading options you would need to load the entire account graph just to merge the changes on the account itself, although contact and asset are not changed (or worse, depending on how you do it, you may here revert changes on them made by somebody else in their dedicated methods in the meantime if you just use the detached instances contained in the account).
Regarding auto-generated ids, you don't have to specify them yourself, just take them from the saved entities (Hibernate will set it there). It is important to take the result of the repository's save method if you plan to use the updated entity afterwards, because merge operation always returns the merged copy of the passed-in instance, and if there are any newly persisted associated entity instances in the updated detached graph, their ids will be set in the copy, and the original instances are not modified.
Please consider the following (partial) class:
#Entity
#Table(name = "account", catalog = "storeman", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class Account implements java.io.Serializable {
private Integer id;
private String email;
private String displayName;
...
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
#Column(name = "email", unique = true, nullable = false, length = 80)
public String getEmail() {
return this.email;
}
#Column(name = "display_name", nullable = false, length = 50)
public String getDisplayName() {
return this.displayName;
}
public static Account lookup(String email, Session session){
return (Account)
session.createCriteria(Account.class)
.add(Restrictions.eq("email", email))
.uniqueResult();
}
}
As you can see, it maps the account table of mydb that uses id as the primary key. However, when searching for Account in db I want use the email as selection criteria. To do so I have added a static method named lookup that performs my query. That works fine as far as I can see.
However I would like to ask whether that kind of approach is good practice when working with hibernate (dynamic web project using JDBC) or not and what kind of issues this can bring (if any).
Passing a session (or EntityManager, or pretty much any other service-object) around is not good practice in my book. Do this enough and you'll find yourself having trouble gathering and controlling where you call the database. The Service-objects should be the ones "using" the data-objects, not the other way around.
The correct thing to do is to create a Repository-service, which contains the means for getting the session/EntityManager, and also methods that perform the JPA-operations for this Entity/functional area.
So, I would move the query out of the Entity class, and into a Repository (the class where this method is called, the one that holds the session, sounds like a place to start.)
It would be better if you create a DAO class say AccountDAO for your model Account and move lookup method to AccountDAO.