Would like to make my database search more efficent - java

Simplified, i have the following three entities:
Project:
#Data
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
// more attributes
#NotNull
#ManyToOne
private Customer customer;
}
Customer:
#Data
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
// more attributes
}
DailyEntry:
#Data
#Entity
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
// more attributes
#ManyToOne
private Project project;
}
So the project has a customer as an attribute and the dailyEntry has a project as an attribute.
I would like to get all dailyEntries related to a customer. So i need to get all the projects related to the customer first and then get all the dailyEntries related to the projects.
I can achieve that with the following code:
for (Customer customer : customerRepository.findAll()) {
for (Project project : projectRepository.findByCustomerId(customer.getId())) {
for (DailyEntry dailyEntry : dailyEntryRepository.findByProjectId(project.getId())) {
// can do sth with all the dailyEntries related to the customer here
}
}
}
}
But achieving that with 3 for-loops seems pretty bad/unefficient, because it has a cubic complexity. Is it really as bad as i think and is there a better way to do it without changing the database model?
Edit: I tried implementing a findByCustomer query inside the DailyEntryRepository.
#Query("SELECT dailyEntry FROM DailyEntry dailyEntry WHERE dailyEntry.project IN (SELECT pro FROM Project pro WHERE pro.customer.name = ?#{customer})")
List<DailyEntry> findByCustomer(Customer customer);
So the query tested in an SQL editor works, but im having problems with passing the customers argument inside the repository. Customer is not an attribute of daily entry, so it does not recognize it. I tried it with the ?#{customer} annotation as above, tried to use #Param("customer") Customer customer, but nothing worked. How can i pass an argument into an SQL query if that argument isnt an attribute of the entity?

You are essentially querying all the DailyEntry entities that you have in the database (unless there are entries without a project and projects without a customer). You should use dailyEntryRepository.findAll() and retrieve all data with a single SQL query instead of using two nested loops which will result in multiple SQL queries (unless you have setup a cache).

Related

How to do lazy loading for this One to One relationship with JPA and Hibernate?

I'm learning lazy loading with JPA and Hibernate in a Spring Boot project. It is a simple project with 2 tables: student and passport. It is a One to One relationship and I see that the lazy loading is not working.
This is the code
student entity:
import javax.persistence.*;
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#OneToOne(fetch = FetchType.LAZY)
private Passport passport;
public Student() {
}
}
Passport entity:
import javax.persistence.*;
#Entity
public class Passport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String number;
public Passport() {
}
}
I'm running this method using debugger from Intellij and It looks like an eager loading because it select students and passports when this entityManager.find(Student.class, 20001L) is called. How can I do this to be a lazy loadung? Thank you!
In LAZY loading hibernate will try to make either a proxy object containing the id of the LAZY loaded entity.In order to do it needs to get the Foreign key in the Student table,which is not available in your example because you didn't specify the #JoinColumn, and depending on the value it will decide to either make a proxy object wrapping the passeport object and ready to be queried ,or assign null to it.
Since there's no foreign key present in the Student table ,hibernate will always treat that OneToOne EAGER ,because it has no clue if a passeport associated with student exists or not.

Java Spring Hibernate JPA PostgreSQL Avoid saving duplicate rows / records

I'm very green when it comes to databases. I feel like this is probably a pretty common database problem, I can't seem to find the correct search terms to find my answer.
My issue is "duplicate" rows in a table. I'm trying to save restaurant food menus in a database, and for the most part its working alright. I have a object called RestaurantWeek, which contains a list of RestaurantDay objects and each day contains a list of RestaurantCourse objects. They get saved as such in the database: image. "weeks_days" and "days_courses" tables are the link between the "weeks", "days" and "courses" tables.
Now the problem comes when many days can have the same "course". Almost every single day has "Salad" as a course, so what ends up happening is I have 12 identical rows in my "courses" table, the only exception being the id column: image. So now the question is, how can I tell JPA or Hibernate to use the existing "Salad" row in my "courses" table instead of inserting a new one every time? Is it possible to do this by adding some specific annotation to my objects or their properties?
I have tried setting the "name" property on "RestaurantCourse" to unique with #Column(unique=true) but then I get errors about hibernate trying to save multiple courses with the same name (since name must be unique). I have tried grabbing the "courses" table when saving data and using the same id multiple times, but then I get errors about hibernate trying to save multiple courses with the same id (since id must be unique).
Is it even possible to fix this "easily", such as with few specific annotation I'm in the unknown about? Do I need to change something else about how my data is saved to the database, such as the classes, the annotations, or the way I'm trying to save?
Here are my classes.
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="weeks")
public class RestaurantWeek {
#Id
private long id;
private Date saveDate;
private String weekName;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantDay> days;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="days")
public class RestaurantDay {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String day;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantCourse> courses;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
#Entity
#Table(name = "courses")
public class RestaurantCourse {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
private String price;
private String type;
#Type(type = "list-array")
#Column(name = "tags", columnDefinition = "text[]")
private List<String> tags;
}
And what I'm using to save:
#Repository
public interface RestaurantMenuRepository extends JpaRepository<RestaurantWeek, Long> {
}
public class RestaurantMenuServiceImpl implements RestaurantMenuService {
#Autowired
private RestaurantMenuRepository restaurantMenuRepository;
#Override
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
return this.restaurantMenuRepository.save(restaurantWeek);
}
}
Thanks in advance.
Yes is posible, you must use existing entity. But use in this method
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) parameter RestaurantWeek is not correct try put this method some dto class with need field to create entity class, additionally pass the parameter to available identify courses entity find which you can doing relationship and add to days entity.
No pass all parameter every time!
Alright, finally found the correct search terms and found the answer. Resolution was a combination of serob's answer and a bunch of googling.
In RestaurantDay I changed #OneToMany to #ManyToMany.
I created repository interfaces for RestaurantDay and RestaurantCourse.
When saving the course, I save the courses first, then the days, and finally the week, while grabbing all the new ids.
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
for (RestaurantDay day : restaurantWeek.getDays()) {
for (RestaurantCourse course : day.getCourses()) {
RestaurantCourse dbCourse = this.restaurantCourseRepository.findCourseByName(course.getName());
if (dbCourse == null) {
course.setId(this.restaurantCourseRepository.save(course).getId());
}
else {
course.setId(dbCourse.getId());
}
}
this.restaurantDayRepository.save(day);
}
return this.restaurantMenuRepository.saveAndFlush(restaurantWeek);
}
Try #NaturalId, this would make your name an Id for the Course entity:
https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/

Dont resolve a #ManyToOne field during a query

I have the following DailyEntry property:
#Data
#Entity
#JsonInclude(JsonInclude.Include.NON_NULL)
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private LocalDate date;
private LocalTime startTime;
private LocalTime endTime;
private Duration breaks;
private String performanceRecord;
private EntryStatus status;
#ManyToOne
private Project project;
#ManyToOne
private Employee employee;
}
I iterate over all DailyEntries of a project:
for (DailyEntry dailyEntry : dailyEntryRepository.findByProjectId(project.getId())) {
// do something
}
This automatically generated query defined in the DailyEntryRepository is being used:
List<DailyEntry> findByProjectId(#Param("id") Long id);
As you see in the entity definition, DailyEntry has two ManyToOne relations to Project and Employee. That means that for each DailyEntry i get from dailyEntryRepository.findByProjectId(project.getId()) two SQL queries on top of that are being used to resolve the Employee and Project. But i dont actually need them in this case. Is there a way to tell the system to not resolve the Employee and Project when i call dailyEntryRepository.findByProjectId(project.getId())? Cause i assume that would improve performance because that would reduce the amount of SQL queries by a lot.
You can try lazy loading Project and Employee. Lazy loading will fetch the items only when required.
#ManyToOne (mappedBy = "id", fetch = FetchType.LAZY)

Complex select on EAV model Spring data jpa

I have a Product Entity like below (It's simple version)
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "product")
private List<ProductAtt> attributes;
}
Each Product could have one or more Attribute. Attribute look likes below
#Entity
#Table(name = "attribute")
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
}
So I create a relation entity like below with extra value property
#Entity
#Table(name = "product_att")
public class ProductAtt implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn
private Product product;
#ManyToOne
#JoinColumn
private Attribute attribute;
private int value;
}
Now I want to find all products that have some attributes with custom values. For example all products that have attribute 1 with value 3 and attribute 2 with value 40 and ... .
What is the simplest and most efficient query to do that?
Since the number of attributes to query is not known at design time, one of the dynamic query mechanisms supported by Spring Data JPA will have to be used. The query can certainly be built using the JPA Specification or Criteria APIs.
If using QueryDSL support, subqueries with exists can be used. The following example shows how this can be done (assuming Java 8 and QueryDSL 4).
interface ProductRepository
extends CrudRepository<Product, Long>
, QueryDslPredicateExecutor<Product> {
default Iterable<Product> findAllByAttributes(final Map<String, String> attributes) {
final QProduct root = QProduct.product;
BooleanExpression query = root.isNotNull();
for (final String attribute : attributes.keySet()) {
final QProductAttribute branch = root.attributes.any();
final BooleanExpression subquery = branch.attribute.name.equalsIgnoreCase(attribute)
.and(branch.value.equalsIgnoreCase(attributes.get(attribute)));
query = query.and(JPAExpressions.selectFrom(QProductAttribute.productAttribute).where(subquery).exists());
}
return findAll(query);
}
}
It should be noted that the database design is such that performance problems are bound to happen, because the same table (ProductAttr) is included as many times as there are attributes to search by. This is not a problem of QueryDSL, JPA, Hibernate, SQL or the database server but the data model itself (also known as the EAV model).

Issue with JPA #MappedSuperclass in a separate jar with Hibernate

My problems come from the fact that I am trying to reuse a mapped superclass that contains some basic fields such as a Long Id.
This mapped superclass looks like this:
#MappedSuperclass
public abstract class AbstractBaseEntity {
protected Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", nullable = false, unique = true, columnDefinition = "int")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
It sits in a jar so that everyone can reuse it easily.
Apparently, all works perfectly except when entities that extend it actually have relationships between them and you try to get data using queries based on those relationships.
Example:
Say you have entity Organization that has one or more User (s):
#Entity
#Table(name = "Organizations")
public class Organization extends AbstractBaseEntity {
private Set<User> users;
#OneToMany(mappedBy = "organization", fetch = FetchType.LAZY)
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
#Entity
#Table(name = "Users")
public class User extends AbstractBaseEntity {
private Organization organization;
#ManyToOne
#JoinColumn(name = "Organization_ID", nullable = false)
public Organization getOrganization() {
return organization;
}
public void setOrganization(Organization organization) {
this.organization = organization;
}
}
Now here's the problem: If I use a DETACHED organization object as a parameter to a query like this:
SELECT u from User u where u.organization = ?1
then Hibernate throws this following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.Organization
This doesn't make any sense to me, it shouldn't require an attached entity for this kind of query since all it needs is its id.
If, however, i take out AbstractBaseEntity from the jar and put it in the same project as Organization and User, it all works perfectly, with detached Organizations and all...
This looks very much like a bug to me. Any clues on how to work around it?
PS. I've tried explicitly specifying the AbstractBaseEntity as a class in persistence.xml as specified here - JPA #MappedSuperclass in separate JAR in Eclipse (weird but worth a try) ...doesn't work
Sorry to say, but I would assume you simply can not "pull" a MappedSuperclass from a different compilation unit. This is because the JPA provider maybe uses code instrumentation to access the entity fields.
Have you tried to create a clone class in your own workarea?
Regards from Germany,
Thomas
According to JPA spec, if you have jpa classes in separate jar you should add it to the persistence.xml (I don't know, if Hibernate requires that, but you can give it a try). Try to add following entry to your persistence.xml <jar-file>entity.jar</jar-file>
Check this post how to define path in the jar-file tag.
Just a guess, but entities need to implement Serializable. See if that fixes your issue.

Categories

Resources