Multiple Many-to-many relationships in JPA/Hibernate Eager - java

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.

Related

How to map a DTO to multiple entities?

I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.

Entities design JPA

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.

Orchestrating Spring Boot CrudRepositories with foreign key relationships

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.

Hibernate model class and helper method best practices

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.

Hibernate: enforcing unique data members

I am having an issue working with Hibernate and enforcing unique data members when inserting.
Here are my abridged Entity objects:
Workflow:
#Entity
public class Workflow {
private long wfId;
private Set<Service> services;
/** Getter/Setter for wfId */
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "workflow_services",
joinColumns = #JoinColumn(name = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "service_id"))
public Set<Service> getServices() {
return services;
}
Service:
#Entity
public class Service {
private long serviceId;
private String serviceName;
/** Getter/Setter for serviceId */
...
#Column(unique=true,nullable=false)
public String getServiceName() {
return serviceName;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "service_operations",
joinColumns = { #JoinColumn(name = "serviceId") },
inverseJoinColumns = { #JoinColumn(name = "operationId") })
public Set<Operation> getOperations() {
return operations;
}
Operation:
#Entity
public class Operation {
private long operationId;
private String operationName;
/** Getter/Setter for operationId */
#Column(unique=true,nullable=false)
public String getOperationName() {
return operationName;
}
My issue:
Although I have stated in each object what is SUPPOSED to be unique, it is not being enforced.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
Currently I am getting repeats within my Services and Operations tables.
I have tried using the annotation:
#Table( uniqueConstraints)
but have had zero luck with it.
Any help would be greatly appreciated
The unique or uniqueConstraints attributes are not used to enforce the uniqueness in the DB, but create the correct DDL if you generate it from hibernate (and for documentation too, but that's arguable).
If you declare something as unique in hibernate, you should declare it too in the DB, by adding a constraint.
Taking this to the extreme, you can create a mapping in which the PK is not unique in the DB, and hibernate will throw an exception when it tries to load one item by calling Session.load, and sudently finding that there are 2 items.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
I think you're asking Hibernate to detect duplicate objects when you add them to the Set, yes? In other words, when you put an object in the Set, you want Hibernate to go look for a persistent version of that object and use it. However, this is not the way Hibernate works. If you want it to "reuse" an object, you have to look it up yourself and then use it. Hibernate doesn't do this.
I would suggest having a helper method on a DAO-like object that takes the parent and the child object, and then does the lookup and setting for you.

Categories

Resources