Spring JPA most efficient way to load ManyToOne relationship? - java

I have an entity called StoreQuantity, which stores the current in stock quantity of all products/items in a store:
#Data
#Entity
#Table(name = "STORE_QUANTITY")
public class StoreQuantity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_QUANTITY_ID", nullable = false)
private int storeQuantityId;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
#Column(name = "INSTORE_QUANTITY")
private int instoreQuantity;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "STORE_ID", nullable = false)
private Store store;
}
Corresponding Store entity:
#Entity
#Table(name = "store")
public class Store implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_ID", nullable = false)
private int storeId;
#Column(name = "NAME", nullable = false)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "store")
private List<StoreQuantity> storeQuantityList;
}
Im trying to retrieve all the quantities of products in all stores, and export as csv. I currently have thought of two ways of doing so:
Either Retrieve the entire storequantity table in one call, and for each storequantity I print as csv.
public String currentStoreQuantitiesCSV() {
List<StoreQuantity> storeQuantityList = storeQuantityRepository.findAllByOrderByStoreDesc();
for (StoreQuantity storeQuantity : storeQuantityList) {
//StoreId
csvString.append(storeQuantity.getStore().getStoreId()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
Or I call them by store:
public String currentStoreQuantitiesCSV() {
List<Store> storeList = storeRepository.findAll();
for (Store store:storeList){
List<StoreQuantity> storeQuantityList = store.getStoreQuantityList();
for (StoreQuantity storeQuantity : storeQuantityList) {
//Store Name
csvString.append(storeQuantity.getStore().getName()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
}
They both work, now it's just a matter of efficiency and ram utilization. I read by default JPA will eagerly load any ManyToOne relationships: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate
So does this mean if I choose option 1, there will be as many copies of store objects for every storequantity object? This will be extremely bad as I only have 20-or so stores, but thousands and thousands of storequantities, and id each of them are loaded with their own store object it will be very bad. Or will every storequantity point to the same store Objects? I'm only considering method two because that way there wouldnt be a lot of store objects in memory.

I did some testing looking at the stack memory, it seems that JPA will automatically map all ManyToOne relationships to one object. So in this case for example we have one store, and 10 storequantities that have a ManyToOne to that store. JPA will only instantiate one store object and point all 10 storequantity objects to that one store, instead of creating one store for every storequantity object. So option 1 will be the most efficient as we decrease the amount of database calls.

Related

How to save spring entities solely using the foreign key of an associated object

I have two spring entities, job and employer, which have a bidirectional association.
Job Entity
#Entity
#Table(name = "job")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "job_id", nullable = false)
private Integer id;
#Column(name = "title", nullable = false)
private String title;
#ManyToOne(optional = false)
#JoinColumn(name = "employer_employer_id", nullable = false)
#JsonBackReference
private Employer employer;
}
Employer Entity
#Entity
#Table(name = "employer")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Employer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employer_id", nullable = false)
private Integer employerId;
#Column(name = "name", nullable = false)
private String name;
//Mapped by indicates the inverse side of the relationship.
#OneToMany(mappedBy = "employer", orphanRemoval = true)
#JsonManagedReference
private List<Job> jobs = new ArrayList<>();
}
I also have two simple CRUD repositories
Let's say that I have an existing employer object saved in the database. In my jobs service, I want to have a method that creates a new job. My question is, what is the correct Spring boot way to save a new job entry in the database where the employee id foreign key relates back to that existing job in the database.
Here is my first attempt, and it works, however it doesn't seem very efficient. Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save? Is there a way I can avoid making this extra database call, and when we are saving the job to the database, just easily specify an existing employer ID on that new job we are saving to the database? Or is it Spring best practice to have to save the entire employee object here, even if it already exists?
Employer e = employerRepository.findById(0).orElseThrow(IllegalArgumentException::new);
job1.setEmployer(e);
jobRepository.save(job1);
Best way is use getOne so you don't even have to fetch the empoyer
Employer e = employerRepository.getOne(id);
job1.setEmployer(e);
jobRepository.save(job1);
If employer does t exist an exception will be thrown when you save job.
getOne is deprecated in later versions of jpa so use this instead
JpaRepository#getReferenceById(ID)
Good/progressive question:
Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save?
We don't have to! We can:
Employer empler =
entityManager.getReference(Employer.class, 0L);
// handle exception...
Resp. with spring-data:
JPARepository.getReferenceById(...);
"must read" article
jpa-javadoc
spring-data-javadoc
Regarding "how to do it best" (and in which queries it results), i can additionally very recommend:
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
(Where Employer is analogous to Post and Job analogous to PostComment;)

JPA/Hibernate Spring boot-primary key one entity referred as an instance to other entity not working

I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.

How to use derived columns in Spring Data JPA associations

I have an entity TeamActivity:
#Entity
#Table(name = "teams_to_activities")
public class TeamActivity {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "team_id", nullable = false)
private String teamId;
#Column(name = "activity_set_id", nullable = false)
private String activitySetId;
#Id
#Column(name = "scoped_team_activity_id", nullable = false)
private String scopedTeamActivityId;
}
And another entity ActivitySet:
#Entity
#Table(name = "activity_sets")
public class ActivitySet {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Id
#Column(name = "scoped_activity_set_id", nullable = false)
private String scopedActivitySetId;
}
There's no index on any other column besides the PK in both tables.
There's no FK constraint creating a relationship between these tables whatsoever. I have no idea why as this is a legacy system.
Technically, if I fetch a TeamActivity record, I can pick the scope_id and activity_set_id from it and combine them to form a scoped_activity_set_id which would be a valid PK to fetch the corresponding ActivitySet.
I know that TeamActivity -> ActivitySet is a N -> 1 association
I would like to leverage Spring Data JPA features to create an association from TeamActivity to ActivitySet such that when I fetch a TeamActivity from TeamActivityRepository, the corresponding ActivitySet is also returned.
I have created an association like this before using a combination of #JoinColumn and #MapsId but there was actually a single FK to use which is different here where source table has 2 columns I can combine to get the target's key.
If you are fully in control of the database, I may propose you create a Materialized View with the contents you desire from both tables and handle it as any other table with JPA, i.e, create #Entity model and CrudRepository<MVTeamActivitySet, String>.
If you are not fully in control of the database, one easy way to achieve it is to simply create a method that internally executes two lookup queries and retrieves the expected model you want. You will still be using using JPA correctly.
Querying two tables and joining desired fields in the code layer is quite common with denormalized DBs, sometimes you want to avoid the overhead of a Materialized View.
#Override
public TeamActivitySetDto findById(String scopedTeamActivityId) throws DemoCustomException {
Optional<TeamActivity> teamActivityEntity = teamActivityDao.getById(scopedTeamActivityId);
if(teamActivityEntity.isEmpty()) {
throw new DemoCustomException("teamActivity record not found");
}
String scopedActivitySetId =
teamActivityEntity.get().getScopeId() + ":" + teamActivityEntity.get().getActivitySetId();
Optional<ActivitySet> activitySetEntity = activitySetDao.getById(scopedActivitySetId);
if(activitySetEntity.isEmpty()) {
throw new DemoCustomException("activitySet record not found");
}
return TeamActivitySetDto.builder()
.description(activitySetEntity.get().getDescription())
.name(activitySetEntity.get().getName())
.scopedActivitySetId(activitySetEntity.get().getScopedActivitySetId())
.activitySetId(teamActivityEntity.get().getActivitySetId())
.scopedTeamActivityId(teamActivityEntity.get().getScopedTeamActivityId())
.scopeId(teamActivityEntity.get().getScopeId())
.teamId(teamActivityEntity.get().getTeamId())
.build();
}

Hibernate #OneToMany Mapping - issues with deleting records

In my application I would like to put all images in the application in one table. I posted a question last time and someone recommended that I use unidirectional #OneToMany.
I have the following entities which are associated with Image entity
#Entity
#Table(name="promotion")
public class Promotion {
#Id
#Column(name="id")
protected String id;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="itemId")
protected List<Image> images = new ArrayList<>();
}
#Entity
#Table(name="product")
public class Product {
#Id
#Column(name="id")
protected String id;
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="itemId")
protected List<Image> images = new ArrayList<>();
}
#Entity
#Table(name="image")
public class Image{
#Id
#Column(name="id")
private String id = IdGenerator.createId();
#Column(name="itemId")
private String itemId;
#Column(name="title", nullable = false)
protected String title;
#Column(name="filename", nullable = false)
protected String filename;
#Column(name="path", unique = true)
private String path;
#Column(nullable = true)
protected int width;
#Column(nullable = true)
protected int height;
}
Issues am facing now are:
A)
When I use cascade={CascadeType.PERSIST, CascadeType.MERGE} on the images ArrayList attributes I get this exception:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
So I replaced it with cascade=CascadeType.ALL and when I save Product or Promotion the associated Image(s) are saved as well which is cool and that is what I want
B)
The main problem I have now is when I delete a Product or Promotion, its associated images never get deleted. Their associated images stays in the image table.
By using cascade=CascadeType.ALL I expect that when I delete a Product or a Promotion, its images should also be deleted automatically. I tried to delete an image from the database if it will trigger its associated Product or Promotion to be deleted but it didn't since I think it is unidirectional which makes sense. But how come when I delete a Product or a Promotion its associated images don't get deleted
add orphanRemoval = true in both relationships:
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
This will
apply the remove operation to entities that have been removed from the
relationship and to cascade the remove operation to those entities.

Ternary (and n-ary) relationships in Hibernate

Q 1) How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)?
NOTE: I know that JPA 2 has added some constructs for building ternary relationships using maps. However, this question assumes JPA 1 or Hibernate 3.3.x and I don't like to use maps to model this.
(source: grussell.org)
(source: grussell.org)
Ideally I prefer my model to be like this:
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Product product;
//...
}
Q 1.1)
How can we model this variation, in which each Sale item might have many Products?
class SaleAssistant {
Long id;
//...
}
class Customer {
Long id;
//...
}
class Product {
Long id;
//...
}
class Sale {
SalesAssistant soldBy;
Customer buyer;
Set<Product> products;
//...
}
Q 2) In general, how can we model n-ary, n >= 3 relationships with Hibernate?
Thanks in advance.
Q1. How can we model a ternary relationship using Hibernate? For example, how can we model the ternary relationship presented here using Hibernate (or JPA)? (...)
I would remodel the association with an intermediate entity class (and that's the recommended way with Hibernate). Applied to your example:
#Entity
public class Sale {
#Embeddable
public static class Pk implements Serializable {
#Column(nullable = false, updatable = false)
private Long soldById;
#Column(nullable = false, updatable = false)
private Long buyerId;
#Column(nullable = false, updatable = false)
private Long productId;
public Pk() {}
public Pk(Long soldById, Long buyerId, Long productId) { ... }
// getters, setters, equals, hashCode
}
#EmbeddedId
private Pk pk;
#ManyToOne
#JoinColumn(name = "SOLDBYID", insertable = false, updatable = false)
private SaleAssistant soldBy;
#ManyToOne
#JoinColumn(name = "BUYERID", insertable = false, updatable = false)
private Customer buyer;
#ManyToOne
#JoinColumn(name = "PRODUCTID", insertable = false, updatable = false)
private Product product;
// getters, setters, equals, hashCode
}
Q1.1. How can we model this variation, in which each Sale item might have many Products?
I wouldn't use a composite primary key here and introduce a PK for the Sale entity.
Q2. In general, how can we model n-ary, n >= 3 relationships with Hibernate?
I think that my answer to Q1. covers this. If it doesn't, please clarify.
Update: Answering comments from the OP
(...) the pk's fields are not getting populated and as a result I cannot save Sale items in the DB. Should I use setters like this for the Sale class? public void setBuyer(Customer cust) { this.buyer = cust; this.pk.buyerId = cust.getId(); }
You need to create a new Pk (I removed the constructors from my original answer for conciseness) and to set it on the Sale item. I would do something like this:
Sale sale = new Sale();
Pk pk = new Pk(saleAssistant.getId(), customer.getId(), product.getId());
sale.setPk(pk);
sale.setSoldBy(saleAssistant);
sale.setBuyer(customer);
sale.setProduct(product);
...
And then persist the sale.
Also, in the JoinColumn annotations, what column are "name" fields referring to? The target relations' pks or the sale table's own column names?
To the columns for the attributes of the composite Pk (i.e. the sale table's own column names), we want them to get PK and FK constraints.
Are you using database generated primary keys for Customer, Product and SalesAssistant? That might cause an issue since it looks like you're trying to use the actual DB identities rather than letting Hibernate resolve the object references during actual persistence.
The embedded PK above looks odd to me personally but I've not had a chance to try it out. It seems like the columns are overlapping and clobbering each other.
I would think it sufficient to just have the ManyToOne references.
Also, turn on SQL statement debugging and see what's being sent to the DB.

Categories

Resources