So I have 4 tables
An Employer
#Entity
#EqualsAndHashCode(callSuper = false)
#Table(name = "employers")
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "jobPostings"})
#PrimaryKeyJoinColumn(name="employer_id", referencedColumnName = "id")
public class Employer extends User {
#Column(name = "company_name")
private String companyName;
#Column(name = "website")
private String website;
#Column(name = "phone_number")
private String phoneNumber;
#OneToMany(mappedBy="employer")
private List<JobPosting> jobPostings;
}
A City
#Data
#Entity
#Table(name="cities")
#AllArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "jobPostings"})
public class City {
#Id
#Column(name="id")
private int id;
#Column(name="city_name")
private String cityName;
#OneToMany(mappedBy="city")
private List<JobPosting> jobPostings;
}
A Job Position
#AllArgsConstructor
#Data
#Entity
#Table(name="job_positions")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "jobPostings"})
public class JobPosition {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="job_position_id")
private int id;
#Column(name="title")
private String title;
#OneToMany(mappedBy="jobPosition")
private List<JobPosting> jobPostings;
}
And A JobPosting(like a job advertisement)
#Entity
#Table(name="job_postings")
#Data
#NoArgsConstructor
#AllArgsConstructor
//#JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "city", "jobPosition","employer"})
public class JobPosting {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="job_requirements")
private String jobRequirements;
#Column(name="salary_min")
private int salaryMin;
#Column(name="salary_max")
private int salaryMax;
#Column(name="application_deadline")
private LocalDate applicationDeadline;
#Column(name="number_of_openings")
private int numberOfOpenings;
#Column(name="stream_date")
private LocalDate streamDate;
#ManyToOne()
#JoinColumn(name="city_id")
private City city;
#ManyToOne()
#JoinColumn(name="job_position_id")
private JobPosition jobPosition;
#ManyToOne()
#JoinColumn(name= "employer_id")
private Employer employer;
}
I have implemented the necessary mapping for all of them and it is working fine.
However, I want to Join them in a DTO like:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class JobPostingWithJobPositionCityEmployerDto {
private int id;
private String jobRequirements;
private int salaryMin;
private int salaryMax;
private LocalDate applicationDeadline;
private int numberOfOpenings;
private LocalDate streamDate;
private String cityName;
private String title;
private String companyName;
}
to get the fields I want in a more clean way, I am trying to use #Query annotation of springframework.jpa but I can not quite manage it since I just learned about this, the query I am using is:
#Query(value ="Select new kodlamaio.hrms.entities.dtos.JobPostingWithJobPositionCityEmployerDto"
+ "(j.id, j.jobRequirements, j.salaryMin, j.salaryMax, j.numberOfOpenings, j.streamDate, j.applicationDeadline, c.cityName, p.title, e.companyName)"
+ " From Employer e Inner Join e.jobPostings j, "
+ "From City c Inner Join c.jobPostings j, "
+ "From JobPosition p Inner Join p.jobPostings j", nativeQuery = true)
List<JobPostingWithJobPositionCityEmployerDto> getJobPostings();
I dont even know if this is the correct way to do this, I keep getting syntax error, I looked up answers but couldnt quite grasp what they were saying, there were a lot of different scenarios.
So if anybody could help me with this Query and recommend some sources to learn about different commands, I would appreciate it so much, Thanks in advance.
Mapping the result of a query with a DTO using the new keyword in a query only works for JPQL, it will not work for SQL (which is what you are using).
It also looks to me if you are trying to write a too complex query as everything can be achieved/reached through the JobPosting class, which will implicitly do the join when using JPQL.
So instead of your native SQL writing a JPQL should fix it.
Something like
#Query(value ="Select new kodlamaio.hrms.entities.dtos.JobPostingWithJobPositionCityEmployerDto"
+ "(jp.id, jp.jobRequirements, jp.salaryMin, jp.salaryMax, jp.numberOfOpenings, jp.streamDate, jp.applicationDeadline, jp.city.cityName, jp.jobPosition.title, jp. employer.companyName)"
+ " From JobPosting jp)
Which should do the trick. Your JPA provider should be smart enough to figure out what to join and retrieve.
Generaly this is named Projection, I believe that you have already created JobPostingWithJobPositionCityEmployerDto with a constructor of 10 args, with their respective data type of course. And since it is custom to jpa you can't use nativeQuery = true. These modifications should be fine.
#vlad-mihalcea to the rescue
https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/
Related
Let's assume there are two entities:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#ManyToMany(mappedBy = "authors")
private Set<Book> books;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String fileName;
private String fileType;
#Lob
private byte[] data;
#ManyToMany
#JoinTable(
name = "book_authors",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "author_id"))
private Set<Author> authors;
}
The following DTO interface projection is used to query only the columns that are needed.
public interface AuthorView {
String getFirstName();
String getLastName();
Set<BookView> getBooks();
interface BookView {
String getTitle();
}
}
A simple findAllBy query method is declared in the repository:
public interface AuthorRepository extends JpaRepository<Author, Long> {
#EntityGraph(attributePaths = "books")
List<AuthorView> findAllBy();
}
The method executes the following query:
select
author0_.id as id1_0_0_,
book2_.id as id1_1_1_,
author0_.first_name as first_na2_0_0_,
author0_.last_name as last_nam3_0_0_,
book2_.data as data2_1_1_,
book2_.file_name as file_nam3_1_1_,
book2_.file_type as file_typ4_1_1_,
book2_.title as title4_1_1_,
books1_.author_id as author_i2_2_0__,
books1_.book_id as book_id1_2_0__
from
author author0_
left outer join
book_authors books1_
on author0_.id=books1_.author_id
left outer join
book book2_
on books1_.book_id=book2_.id
Even though the projection doesn't contain data, file_name, and file_type properties, they are fetched from the database which is causing performance issues, especially if the files are large.
The problem is that Spring Data JPA fetches the entire entities and uses them to perform a programmatic mapping, according to Thorben Janssen's blog.
Is there any solution to prevent fetching entire entities when using interface-based DTO projections except for writing massive queries?
Recently, I found out Blaze Persistence - Entity View Module, which looks promising.
According to #Christian Beikov's answer, EntityView projections can be used almost like Spring Data Projections with the Spring Data integration, and those projections fetch only the necessary properties. Additionally, there is no need to use #EntityGraph, since the integration handles it ad-hoc by adapting the query generation.
I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.
I am trying to fetch data from all below tables using JOIN query for particular productType and Sizes (column in two diff tables).
I am unable to get the desired result so some guidance would be very helpful.
Please find the details below what I have tried till now.
PK: Primary Key, FK: Foreign Key.
Following is the table structure with tables(mentioned) below has OneToOne mapping to another table.
**MC_Product_Type**:
prod_type_id (PK),
prod_type,
description
|
|OnetoOne
|
**MC_Set_Rules**:
set_id (PK),
prod_type_id (FK),
set_name,
set_type,
condition
|
|OneToOne
|
**MC_Size_Rules**:
prod_rule_id (PK),
prod_type,
Sizes,
set_id (FK),
min_qty,
dimension
|
|OneToOne
|
**MC_Product_Rules**:
prod_rule_id (FK),
prod_type,
allowed_type,
availability,
prod_label,
locations
Entity Classes:
#Table(name = "MC_Product_Type")
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Getter
#Setter
#ApiModel
#JsonInclude(JsonInclude.Include.NON_NULL)
public class ProductType {
#Id
private int prodTypeId;
private String prodType;
private String description;
#OneToOne
#JoinColumn(name="prodTypeId", referencedColumnName="prodTypeId", insertable=false, updatable=false)
private SetRules setRules;
}
#Table(name = "MC_Set_Rules")
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Getter
#Setter
public class SetRules {
#Id
private int setId;
private int prodTypeId;
private String setName;
private String setType;
private String condition;
#OneToOne
#JoinColumn(name="setId", referencedColumnName="setId", insertable=false, updatable=false)
private SizeRulesEntity sizeRules;
}
#Table(name = "MC_Size_Rules")
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Getter
#Setter
public class SizeRulesEntity {
#Id
private int prodRuleId;
private String prodType;
private String sizes;
private int setId;
private int minQty;
private String dimension;
#OneToOne
#JoinColumn(name="prodRuleId", referencedColumnName="prodRuleId", insertable=false, updatable=false)
private ProductRules productRules;
}
#Table(name = "MC_Product_Rules")
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Getter
#Setter
public class ProductRules {
#Id
private int prodRuleId;
private String prodType;
private String allowedType;
private String availability;
private String prodLabel;
private String locations;
}.
Repository:
Value for prod_type = "ELLACUST"
Value for Sizes = "XS","S","M","L","XL"
I used these below queries(one by one) , based on prod_type and Size I need to get data from all the tables.
Note: Sizes is not part of table MC_Product_Type. Data team has informed if they include sizes in this table it will be a redundant data. so sizes is part of MC_Size_Rules table.
I am trying to pass prod_type and sizes dynamically in first two query but getting results only for size 'XS' which is value for 1st row in table MC_Size_Rules even though if I pass other sizes values.
In third query, I am getting exception saying sizes is not part of ProductType.
I need help on what should I change in entity classes, mappings or in query to get data from all the tables when prod_type and correct sizes is passed.
public interface ProductRulesRepository extends JpaRepository <ProductType, String> {
#Query("SELECT pt FROM ProductType pt JOIN pt.setRules s ON pt.prodTypeId = s.prodTypeId JOIN s.sizeRules sr ON s.setId = sr.setId JOIN sr.productRules pr ON sr.prodRuleId = pr.prodRuleId where pt.prodType = :prodType AND pt.setRules.sizeRules.sizes = :sizes")
ProductType findAllByProdTypeAndSizes(String prodType, String sizes);
#Query("SELECT pt FROM ProductType pt JOIN pt.setRules s ON pt.prodTypeId = s.prodTypeId JOIN s.sizeRules sr ON s.setId = sr.setId JOIN sr.productRules pr ON sr.prodRuleId = pr.prodRuleId where pt.prodType = ?1 AND pt.setRules.sizeRules.sizes = ?2"))
#Query("SELECT pt FROM ProductType pt JOIN pt.setRules s ON pt.prodTypeId = s.prodTypeId JOIN s.sizeRules sr ON s.setId = sr.setId JOIN sr.productRules pr ON sr.prodRuleId = pr.prodRuleId")
ProductType findAllByProdTypeAndSizes(String prodType, String sizes);
exception : sizes is not defined in ProductType
First of all you are misusing #Query annotation your variable interpolation is wrong cf "?2 you should use it like this
#Query("select from User u where u.id = :id")
findUserById(#Param("id") String id)
Second: the return type of your methods are wrong, you are selecting multiple rows so your retrun type should be a collection
third: Spring provides you with a feature called Spring named queries. It allows spring guessing the associated sql from the method's name
and finally you can try something like this
Collection<ProductType> findByProdTypeAndSizeRulesSizes(String prodType, String sizes);
hibernate orm will handle fetches for you the associated data in the related objects (#OneToOne relations)
I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;
I have what I thought was a straight forward relation in JPA. Looks like this. CompanyGroup:
#Entity
#Table
public class CompanyGroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#JoinColumn(name = "companies")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Company> companies;
}
Company:
#Entity
#Table
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "name")
private String name;
#JoinColumn(name = "users")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<User> users;
#Id
#GeneratedValue
private Long id;
}
User:
#Entity
#Table
public class User {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#Column(name = "email")
private String email;
#Id
#GeneratedValue
private Long id;
}
I have omitted setters, getters, etc.
This is not working. I'm trying to save a CompanyGroup(Has 2 companies, each company has 2 users, all entities are unique) to a fully empty database.
I persist this using Spring-Data, accessed in a service like this:
#Service
public class ConcreteCompanyGroupService implements CompanyGroupService {
#Autowired
private CompanyGroupRepository repository;
#Transactional
#Override
public void save(CompanyGroup group) {
repository.save(Collections.singleton(group));
}
}
When I try to call this method I receive this:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "User"
Position: 13
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2458)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2158)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:291)
Hopefully I have done something stupid that someone can find quickly. I don't know how to solve this.
EDIT:
The driver in my pom.xml:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1211</version>
</dependency>
Your entity maps across to a table name that is an SQL reserved keyword (User). Sadly for you, your chosen JPA provider does not automatically quote the table name identifier, and so you get exceptions when referring to the table.
Solution is either to quote the table name yourself in the #Table annotation, or change the table name to not be a reserved keyword. Alternatively use a JPA provider that auto-quotes such reserved keywords for you (e.g DataNucleus)
Solution 1: As Pascal mentioned, you have to escape the table name with backslash like:
#Entity
#Table(name="\"User\"")
public class User {
...
}
Solution 2: Rename your table's anme with another name (Users)
#Entity
#Table(name="Users")
public class User {
...
}
Solution 3: Add a suffix to the table's name:
#Entity
#Table(name="APP_User")
public class User {
...
}
Solution 4: Change the entity name, e.g. ApplicationUser
#Entity
public class ApplicationUser {
...
}
The reason
PostgreSQL as some reserved SQL Key Words. For example: ABORT, ALL, ARRAY, CACHE, CUBE, USER, ... Those tokens are in the SQL standard or specific to PostgreSQL
Use the #Table annotation or change your class name from User to something else as User is a reserved keyword in sql.