Multi table association with Spring data JPA - spring-boot

I'm trying to create DB solution for my Customer Offer requirement. I have to read a file and store the data in multiple tables which are mostly associated to each other.
I've created Entity classes and their associations as below. But with the current approach I see an issue expanding the offers from Coupons to Rewards. If I create another entity for Rewards then I need to update my CustomerEntity class and add another #OneToMany association with the new entity.
#Entity(name = "METADATA")
public class MetadataEntity {
Metadata Id
Offer description
//Setters & Getters
}
#Entity(name = "CUSTOMER")
public class CustomerEntity {
Customer Id
Customer Name
CouponEntity (OneToMany - Join column Coupon Code)
//Setters & Getters
}
#Entity(name = "PROFILE")
public class ProfileEntity {
Profile Id
CustomerEntity (OneToOne - Join Column Customer Id)
//Setters & Getters
}
#Entity(name = "COUPON")
public class CouponEntity {
Coupon Code
Amount
Expiry
//Setters & Getters
}
#Entity(name = "OFFER")
public class OfferEntity{
Id
MetadataEntity(ManyToOne - Join column Metadata Id)
ProfileEntity(ManyToOne - Join column Profile Id)
//Setters & Getters
}
And from my DAO class I'm using OfferRepository class associated with Offer.
#Repository
public interface OfferRepository extends JpaRepository<OfferEntity, String> {}
#Autowired
OfferRepository offerRepo;
public class DaoImpl {
createOffer(OfferEntity offer){
offerRepo.save(offer);
}
readOffers(){
offersList = offerRepo.findAll();
for-each offer:offersList {
//access all customer information & offer information (could be a coupon or rewards)
offer.getProfile().getCustomer().getCoupons();
}
}
}
I'm trying to identify a solution where I don't need to update any of my existing entity classes in if I'm expanding the Offers.

I would suggest you create a base class for Coupon and Rewards. Let's call it Benefit.
This can then be used as the relationship in the Customer class.
The customer class then could have a method to get either Coupons or Rewards depending on the class object you pass to that method like:
T List<T> getBenefits(Class<T> type)

Related

How to update other table when inserting with OneToOne using Spring JPA

I have two tables joined with a OneToOne relationship, one side exists in the database. When I insert the other side I want the first side's foreign key column to update so that it knows about the relationship without having to do it manually. Is this possible.
Here's my simplified example, I am using #MappedSuperclass because I have some shared fields in most of my Entities I included it here just in case it's causing an issue.
The base entity
#MappedSuperclass
#Data
public abstract class BaseEntity {
//defines some common fields I have in all my entities such Id
}
Abstract Image class
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
#Data
public abstract class Image extends BaseEntity {
//defines some other fields
public abstract UUID getTypeId();
}
UserProfilePhoto
#Entity
#Data
public class UserProfilePhoto extends Image {
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userProfilePhoto")
private Profile profile;
#Override
public UUID getTypeId() {
return user.getId();
}
}
Profile
#Entity
public class Profile extends Base {
//defines some other fields
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn()
private UserProfilePhoto profilePhoto;
}
I'm looking for the following behavior:
When UserProfilePhoto is saved to image table (with Profile ID) the corresponding ImageId is updated in Profile
When Profile is Deleted the corresponding UserProfilePhoto is deleted from image table.
When UserProfilePhoto is deleted Profile remains but the foreign key column is nulled.
From researching this I think it's possible but it's a matter of getting the annotations correct. I've tried different combinations with no luck. Is what I'm looking for even possible?
No, it is not possible the way you describe it.
Any bidirectional relationship in JPA is controlled exclusively by the side indicated by mappedBy, so you need to update that side in your code, in order to have it persisted.
You can do that by invoking the other side in the setter, or by editing the other side in the first place.

Hibernate - #ManyToOne on Entities that extends from Abstract class

I have a similar structure already existing in my database and it has many data:
#MappedSuperclass
public abstract class Person {
#Id
Long id;
#Column(name="COMMONFIELD")
String commonField;
}
#Entity
#Table(name = "WOMAN")
public class Woman extends Person {
.....
}
#Entity
#Table(name = "MAN")
public class Man extends Person {
.....
}
This code will work with two different tables WOMAN and MAN where both will contain the common fields defined in the abstract class Person.
Now I will like to have a class Car that has an owner which can be either a woman or a man. A person can also be the owner of several cars. So I declare it like this:
#Entity
#Table(name = "CAR")
public class Office {
....
#ManyToOne
#JoinColumn(name = "IDPERSON")
Person owner;
}
The issue I encounter is that I cannot create an attribute linked to a Mapped-Superclass.
Of course, I could solve this issue simply by defining Person as an entity and declaring it to be InheritanceType.SINGLE_TABLE and Woman and Man with a discriminator this way they could share the same table and it will work. However, this solution is not possible for me because the data is already there and there are many dependencies from it, so I cannot change the WOMAN or MAN table.
Is there a way to use a discriminator in this case too, so that Hibernate will know in which table it has to look for the person?
Thank you very much in advance.
So I couldn't find any way to tell hibernate to use a discriminator on a specific attribute.
So what I did, I created a Gender Enum
enum Gender {
WOMAN, MAN;
}
And I added it as an attribute of a Car. I also wired the woman and the man service and created a getter method for the owner that will decide which service to use based on gender.
#Entity
#Table(name = "CAR")
public class Office {
....
#Column(name = "PERSONID")
Long personId;
#Column(name = "GENDER")
Gender gender;
#Autowired
#Transient
WomanService womanService;
#Autowired
#Transient
ManService manService;
Optional<Person> getOwner(){
switch(gender){
case WOMAN: return womanService.findById(personId);
case MAN: return manService.findById(personId);
default: return Optional.empty();
}
}
}
I'm aware this solution sucks but it's the best I could come up with to avoid redefining other entities.

how to write the JpaRepository for tables which has composite keys

Please refer attached screenshot to understand the table structure.
Empd_Id is the primary key in 'Employee' table which in turn becomes as a part of composite key along with 'product_id' in table called 'product'.
Any employee can have multiple products so in that case it becomes 'One-to-Many' relationship between 'Employee-Product' tables. Now I'm confused whether I need to write just 1 JpaRepository interface i.e. for employee or 2 JpaRepository interfaces (1 for Employee and another for Product). My gut feeling is just 1 interface for Employee table but how???
Following is my code snippet:-
1st JPA repository interface
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
Entity:-
#Entity
#Table(name="product")
public class Product{
#EmbeddedId
private EmpProd empProd;
#Column(name="product_name")
private String commerceUserId;
#Column(name="description")
private String description;
For composite keys:-
#Embeddable
public class EmpProd implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull
#Column(name="emp_id")
private String empId;
#NotNull
#Column(name="product_id")
private String productId;
2nd Jpa repository interface
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
Entity class:-
#Entity
#Table(name="employee")
public class Employee{
#Id
#NotNull
#Column(name="emp_id")
private String empId;
#Column(name="first_name")
private String firstName;
Though, I have written 2 separate JPA repositories, I strongly believe there will be need for just 1, the main one i.e.
public interface MyMainDataRepository extends JpaRepository {
}
But I do not know to related both entity classes and fetch data from using single Jpa repository as I'm new to Spring Data JPA. I would really appreciate if someone can help me here. Thanks
The two entities Product and Employee don't have any connection as far as JPA is concerned. Therefore you can't access both through a single repository.
If for example, Product would have an actual reference to an Employee you could use a ProductRepository to load Products and navigate from there to the referenced Employees.
But even if that might be feasible, I'd guess that Product and Employee should be considered different aggregates and therefore, should have their own repository each. See Are you supposed to have one repository per table in JPA? for more information on that question.
Given the entities, your repositories look just fine. Note that the entities do look atypical due to the use of String productId instead of Product product.
If you wanted to fetch the employee details, you need the following interface,
public interface MyMainDataRepository extends JpaRepository<Employee, String> {
}
If you wanted to fetch the product details, you need the following interface,
public interface MyRepository extends JpaRepository<Product, EmpProd> {
}
The employee is related to product table, the iteration happens via product and related employees. From this, you can not access the employee table directly and retrieve the employee results from MyRepository interface.

SpringBoot: Is this correct way to save a new entry which has ManyToOne relationship?

I have two entities Person and Visit
Person has OneToMany relationship with Visit.
I was wondering if I want to save an new entry of Visit, and interm of using RestController. Is my approach correct? Or is there another way which is more efficient?
So I have the following controller which takes a VisitModel from the RequestBody, is it a correct way to call it like so?
VisitModel has the ID of person, and the needed properties for the Visit entity. I use the ID of person to look up in the personRepository for the related Person entry, whereafter I issue it to a new instance of Visit and then use the visitRepository to save it.
#RequestMapping(value="", method=RequestMethod.POST)
public String checkIn(#RequestBody VisitModel visit) {
Person person = personRepository.findById(visit.personId);
Visit newVisit = new Visit(visit.getCheckIn, person);
visitRepository.save(newVisit);
return "success";
}
The Visit entity looks as following
#Entity
public class Visit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonProperty("check_in")
private Date checkIn;
#JsonProperty("check_out")
private Date checkOut;
#ManyToOne
#JoinColumn(name="personId")
private Person person;
public Visit(Date checkIn, Person person) {
this.checkIn = checkIn;
this.person = person;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public Person getPerson() {
return person;
}
}
I want to know of the following approach is correct. Or is there another way which is better?
You don't need to get a Person from the database to associate it with a Visit, of course. Because of, you need to have only id of a Person to save it in the foreign key column personId.
If you use JPA EntityManager
Person person = entityManager.getReference(Person.class, visit.personId);
for Hibernate Session
Person person = session.load(Person.class, visit.personId);
This methods just create a proxy and don't do any database requests.
With Hibernate Session I used new Person(personId) as #MadhusudanaReddySunnapu suggested. Everything worked fine.
What is the difference between EntityManager.find() and EntityManger.getReference()?
Hibernate: Difference between session.get and session.load
Yes, that seems to me to be the standard way to map a bidirectional relationship. EDIT: The personId column points to the "id" field of the Person entity.Eg:
#Id
private Long id;
UPDATE: 1: The VisitModel is a 'DTO' or Data Transfer Object. Any separate package is fine. You could consider putting them into a separate jar, so that anyone using your API (with java) can use the jar to create the data before making the call. 2) The way you save it is fine as far as I can see.

Spring MongoDB + QueryDSL query by #DBRef related object

I am using spring-data-mongodb and querydsl-mongodb to perform more flexible queries.
My application has users and orders.
An user can have multiple orders, so my models looks like this:
public class User {
#Id
private String id;
private String username;
//getters and setters
}
public class Order {
#Id
private String id;
#DBRef
private User user;
//getters and setters
}
As you can see, there is an has-many relationship between users and orders.
Each order is assigned to an user, and the user is stored in #DBRef public User user attribute.
Now, lets say that an user has 10,000 orders.
How can i make the query to get all orders that belongs to an specific user ?
I have the OrderRepository:
public interface OrderRepository extends MongoRepository<Order, String>,
QueryDslPredicateExecutor<Order> {
}
I tried this solution but it doesnt return anything:
QOrder order = new QOrder("order");
Pageable pageable = new PageRequest(0, 100);
return userRepository.findAll(order.user.id.eq(anUserId), pageable);
I need to use querydsl because i want to build a service that can query orders by more many prameters than userid. For example i want to get all orders that belongs to user with specific username.
No need for QueryDSL when searching by ID. In OrderRepository, create an interface method:
public List<Order> findByUser(String userId);
Request example: curl http://localhost:8080/orders/search/findByUser?userId=5a950ea0a0deb42729b570c0
* I'm stuck on how to query orders, for example, of all users from certain city (some mongo join with user and address).

Resources