Hibernate - Embedding an aggregated Column in Entity [duplicate] - java

This question already has an answer here:
How to use OneToOne with Hibernate Formula
(1 answer)
Closed 7 years ago.
I am having a problem concerning hibernate annotation mapping configurations. Imagine the following two classes on a social media platform:
#Entity
class User {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany
private List<Post> posts; //lazy-loaded, because of big amounts
}
#Entity
class Post {
#Id
#GeneratedValue
private Long id;
//large amounts of data
private String text;
private Date date;
#ManyToOne
private User author;
}
The most interesting information in my Use Case is the most recent Post every User wrote. I could read in the Users and append a custom query just like this:
SELECT mx.userId,text,mx.date FROM
(SELECT user.id AS userId,max(date) AS date
FROM post,user
WHERE post.author=user.id
GROUP BY user.id) mx,
Post p
WHERE p.author=mx.userId
AND p.date=mx.date;
My Question now is, how I can embed the latest post in its mapped user as a field.
New User:
#Entity
class User {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany
private List<Post> posts;
//#Filter?
//#Query?
private Post latestPost;
}
I don't want to Join the data from my 'latest-post'-aggregation with my User-Objects in Java. This does not taste right. So I am searching for a solution to embed this aggregated field into my base-entity. Where can I specify a custom retrieval of User-Objects in Hibernate to add such an aggregated field?
You could imagine a similar problem with e.g. embedded sums of trade-amounts of a specific customer in a CRM.
I hope, i could illustrate my thoughts and problem. Thanks in advance!

JPA itself does not provide such an annotation.
But the Hibernate specific Where annotation does exactly what you want. The only disadvantage - it needs a collection to work, which you can workaround with a specific getter:
#OneToMany(mappedBy = "author")
#Where("date = (SELECT max(post.date) FROM post GROUP BY post.author)")
private Collection<Post> latestPost;
public Post getLatestPost() {
return latestPost.isEmpty() ? null : latestPost.iterator().next(0);
}

Related

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/

Designing hierarchy of parent child relationships with spring data jpa

I am trying to build a to-do log keeper.
I am using java spring-boot with data-jpa which is built on hibernate.
I want a user to have several projects the user works on. Every project then has several tasks associated with it and the user tracks how much time was spent per a task by completing short atomic units of work (log entries).
So far I ended up building the most naive implementation of this system. It looked like several levels of one to many hierarchy: user->projects->tasks->entries. The current db implementation is based on a schema like this
Code for entity classes (getters setters constructors and some annotations are omitted for brevity):
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
#Entity
public class User extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Project> projects;
}
#Entity
public class Project extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "project", fetch = FetchType.LAZY)
private List<Task> tasks;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
}
#Entity
public class Task extends AbstractEntity {
#Column
private String name;
#OneToMany(mappedBy = "task", fetch = FetchType.LAZY)
private List<Entry> entries;
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
}
#Entity
public class Entry extends AbstractEntity {
#Column
private Integer duration;
#Column
private LocalDateTime finish;
#ManyToOne
#JoinColumn(name = "task_id")
private Task task;
}
I want to be able to provide functionality for a user to view all the log entries in a user specified time frame. I added jpa repository like this:
public interface EntryRepository extends JpaRepository<Entry, Integer> {
#Query("SELECT e FROM Entry e WHERE (e.task.project.user.id=:user_id) AND " +
"(e.finish BETWEEN :from AND :to)")
List<Entry> getAllForUserInDateRange(#Param("from") LocalDateTime from,
#Param("to") LocalDateTime to,
#Param("user_id") int userId);
}
1) Is it correct to say that this query is inefficient? I was thinking performing a fetch like this from a database is inefficient because the query cannot take advantage of indexes. Since there is no foreign key user_id in the Entry table every row is being looked up and the chain entry->task->project->user is being followed. I end up with linear complexity instead of logarithmic.
2) What is a better way to solve the problem? Is it ok to store the foreign key to the user in the Entry table? If I will want to fetch entries from the database for a particular project or a task, then I will have to add foreign keys to these relationships as well. Is that ok?
You should check real SQL which is being executed. Set org.hibernate.SQL log level to DEBUG and you'll see the statements.
I think for your query you will actuall get three inner joins between four tables. You say the query cannot take advantage of indexes. It absolutely can. Create following indexes:
USER (ID)
PROJECT (USED_ID, ID)
TASK (PROJECT_ID, ID)
ENTRY(TASK_ID, ID)
See Contactenated Indexes from Use the Index, Luke.
With these indexes your joins across four tables will likely use indexes. I won't put my hand in fire for this, but it should work. Check the query plan.
You are right that the chain ENTRY->TASK->PROJECT->USER will be followed, but it should be quite faset with indixes
Your database schema is pretty normalized, which results in three joins across four tables. You could denormalize this schema by bringing, say, user_id to the ENTRY. This may improve query performance, but honestly I doubt this will bring much. You may want to run real-world benchmark before actually switching to this solution.

JPA Hibernate :: Inheritance of an entity, with additional OneToMany Lists

I'm using JPA Hibernate/Spring boot to build a web server with MySQL database, and I'm trying to extend a POJO Entity that looks like this, with additional OneToMany Lists.
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
....Constructors, getters and setters....
}
with this basic user entity, I just wanna make a UserInfo entity with additional information about the user's careers.
#Entity
public class UserInfo extends User {
#OneToMany(cascade= CascadeType.ALL, fetch= FetchType.EAGER)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
And I'm quite confused which inheritance strategy I should choose. I don't think its necessary to make another column or table for this.
Or should I just query twice..?
I'm kinda new to JPA so not sure which is considered as the best practice or design..
Edit:
This is how Career entity looks like. Just in case..
#Entity
#Table(name="career")
public class Career {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private Integer user_id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private String description;
....Constructors, getters and setters....
}
Since extending User table was meaningless(just in my case), I changed the User class like this.
#Table(name="user")
public class User {
#Id
#GeneratedValue
private Integer id;
#Column(nullable=false)
private String name;
#OneToMany(fetch= FetchType.LAZY)
#JoinColumn(name="user_id", referencedColumnName = "id")
private List<Career> careers;
....Constructors, getters, setters......
}
Now I'm trying this with Spring Data JPA, and when I try to show the list of Users with their Careers, it is now querying more than 40 times taking about a minute to show the result.
Is this the N+1 problem..? how can I solve this?
In my opinion the error lies within the model itself. Why should UserInfo extend User? I cannot imagine which attributes or methods the UserInfo should inherit from a User. Typical inheritances would be "Developer" or "Administrator".
Why don't you add UserInfo as a 1:1 relation in your User entity? Another option is to omit UserInfo and put the Careers as a 1:n relation right into your User.
To prevent possible n+1 issues on a growing number of Careers you might want to change the fetch mode. See below
#OneToMany(fetch=FetchType.LAZY,mappedBy="user")
#Fetch(FetchMode.SUBSELECT)
private Set<Career> careers = new HashSet<>();

JPA repository - improve findAll() performances

I am using JPA repository, I need to retrieve a whole Mysql table(40000 records) wich has 5 foreign keys towards smaller tables (500 records). I need one field of each of these 5 tables.
If I call a JPArepository findall(), it takes a few seconds to retrieve all the data.
I need it to be faster. Is there a way to do that?
I don't know what would be the best solution, if it can be done on mysql side, or must be done on Java side.
All the tables are well mapped to JPA entities :
#Entity
#Table(name = "T_CLIENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "code")
private String code;
#OneToOne
private Seller seller;
#OneToOne
private Language language;
#OneToOne
private Address address;
#OneToOne
private Country billCountry;
#OneToOne
private ClientType clientType;
}
Thank you for your answers.
You can choose loading fields from foreign keys tables using EntityGraph mechanism.

Lucene index not updated with Hibernate Search and Spring Data

I am getting started with Hibernate Search/Lucene using Spring Boot and Spring Data, but I am having an issue with the index not getting updated (Checked with Luke tool).
I have 3 classes in my domain. This is Datasheet, my root entity:
#Entity
#Indexed
public class Datasheet
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name")
private String m_name;
#Field(name="description")
private String m_description;
#IndexedEmbedded(prefix = "documents.")
#OneToMany(cascade = CascadeType.REMOVE)
private Set<DatasheetDocument> m_documents;
}
Then DatasheetDocument:
#Entity
public class DatasheetDocument
{
#Id
#GeneratedValue()
private long m_id;
private String m_originalFileName;
#Field(name="componentName")
private String m_componentName;
#IndexedEmbedded(prefix = "manufacturer.")
#ManyToOne
private Manufacturer m_manufacturer;
}
And finally Manufacturer:
#Entity
public class Manufacturer
{
#Id
#GeneratedValue()
private long m_id;
#Field(name="name", analyze = Analyze.NO)
private String m_name;
private String m_website;
}
When I explicitly call startAndWait() on the indexer (org.hibernate.search.MassIndexer), then everything is as expected in the index. It contains the fields name, description, documents.componentName and documents.manufacturer.name.
However, when I now do updates through my #RestController classes that call into Spring Data CrudRepository classes, the index only changes when changing a direct field of Datasheet (E.g. name or description). Changing something to the DatasheetDocument instances does not update the index. Any idea why this might be?
Note that I have tried to add backreferences to the parent. For DatasheetDocument:
#ManyToOne
#ContainedIn
private Datasheet m_datasheet;
And for Manufacturer:
#ManyToMany
#ContainedIn
private Set<DatasheetDocument> m_datasheetDocuments;
But that does not help.
I am using Spring boot 1.0.1 which includes Hibernate 4.3.1. I added Hibernate Search 4.5.1. I see that Lucense 3.6.2 gets added transitively as well.
You need the back references for sure. Without them and in particular without #ContainedIn there is no way for Search to know that it has to update the Datasheet index when the DatasheetDocument instance changes.
Have you added mappedBy to the one to many side?
#OneToMany(cascade = CascadeType.REMOVE, mappedBy="m_datasheet")
private Set<DatasheetDocument> m_documents;
Also, how to you update DatasheetDocument? Can you show the code? Either way, you will need to make the associations bi-directional to start with.
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.openSession()
Object customer = fullTextSession.load( Datasheet.class, datasheetDocument.getDatasheet.getId() );
fullTextSession.index(customer);
fullTextSession.flushIndex();

Categories

Resources