Have a "full Entity" class:
#Entity(name = "vacancy_dec_to_words")
public class VacancyDescriptionToWords {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "vacancy_description_id")
#ManyToOne(cascade = CascadeType.ALL)
private VacancyDescription vacancyDescription;
#JoinColumn(name = "words_id")
#ManyToOne
private Words words;
#Column(name = "qty")
private int qty;
#Column(name = "create_date")
private Date date;
//...getters and setters
In some methods I need use only 2 column from this database: word_id and qty
I try the follow way:
Projections
https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#projections
public interface QtyWords {
Long getWords();
Integer getQty();
}
JpaReposytory:
*Query, that I use tested and it workable, I use him in JpaRepository:
#Repository
public interface SmallVDTWRepository extends JpaRepository<VacancyDescriptionToWords, Long> {
#Query(nativeQuery = true,
value = "SELECT sum(qty), words_id FROM vacancy_desc_to_words WHERE vacancy_description_id IN (" +
"SELECT id FROM vacancy_description WHERE vacancy_id IN (" +
"SELECT id FROM vacancy WHERE explorer_id = :exp))" +
"GROUP BY words_id")
List<QtyWords> getDistinctWordsByExplorer(#Param("exp") long exp);
}
But I get some interesting result when I get list of entities:
List<QtyWords> list = vdtwService.getByExplorerId(72);
I am not get any exceptions, but I have the list with are unknowns objects. This objects contains my data, which I need(qty and words_id), but I cannot get them from him.
Can I use this method (Projection) to implement this task and, in general, how to correctly implement the 'Light Entity' in this case?
Spring provides two mechanisms that can be used to limit data to be fetched.
Projections
Projections can help you to reduce data, retrieved from database, by setting what exactly attributes you want to fetch.
Example:
#Entity
class Person {
#Id UUID id;
String firstname, lastname;
#OneToOne
Address address;
}
#Entity
static class Address {
#Id UUID id;
String zipCode, city, street;
}
interface NamesOnly {
String getFirstname();
String getLastname();
}
#Repository
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
Entity graph
Annotation EntityGraph can help you to reduce amount of queries to database, by setting what exactly related entities you need to fetch.
Example:
#Entity
#NamedEntityGraph(name = "GroupInfo.detail", attributeNodes = #NamedAttributeNode("members"))
public class GroupInfo {
#Id UUID id;
#ManyToMany //default fetch mode is lazy.
List<GroupMember> members = new ArrayList<GroupMember>();
}
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
GroupInfo getByGroupName(String name); //Despite of GroupInfo.members has FetchType = LAZY, it will be fetched because of using EntityGraph
}
There are two types of EntityGraph:
EntityGraphType.LOAD - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated according to their specified or default FetchType.
EntityGraphType.FETCH - is used to specify an entity graph, attributes that are specified by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are treated as FetchType.LAZY.
PS: Also remember that you can set lazy fetch type: #ManyToOne(fetch = FetchType.LAZY) and JPA will not fetching child entities when parent is being fetched.
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'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
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).
I have an entity, that i'd like to join OneToOne with a table with a composite key (Omitting getters/setters):
#Entity
#Table(name = "parent")
public class Parent {
#Id
private String parentId;
#Column(name = "data")
private String data;
#OneToOne
private Child child;
}
And:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
private String parentId;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parentId;
private String username;
}
Parent does not have a notion of the 'username' field in the Child entity. I need to pass this in as criteria. In the DB, the primary key of child is on parentId and username.
If I don't specify a JoinColumn, hibernate attempts to map using fields child_username and child_parentId. If I specify only one Joincolumn, I get a broken mapping. If I specify both JoinColumns, I have no column on parent to specify.
How can I map this class and pass in the username as criteria? (it is coming from authentication data) Or how can I do this in a different way if I'm off track.
You might be able to use a Derived Identity.
The Parent class would remain the same; but you would specify a #OneToOne mapping back to the child's parent and the Child and ChildKey classes would look like this:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
public class Child{
#Id
#OneToOne(mappedBy="child")
private Parent parent;
#Id
private String username;
#Column(name = "data")
private String childData;
}
public class ChildKey implements Serializable {
private String parent; // name matches name of the #Id field and type matches type of Parent #Id field
private String username; // name and type match those of the #Id field
}
Derived identity is discussed in JPA 2.1 spec, section 2.4.1.
What I ended up doing was defining a #Filter on the Child class, like so:
#Entity
#IdClass(ChildKey.class)
#Table(name = "child")
#FilterDef(name = "usernameFilter", parameters = {
#ParamDef( name = "username", type="string")
})
public class Child { ... }
On the Parent class, I annotated the collection with a reference to the filter:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "parentId")
#Filter(name="usernameFilter", condition = "username = :username")
private List<Child> children;
Finally, in my DAO, I parameterized the filter by name like so:
Filter filter = currentSession().enableFilter("usernameFilter");
filter.setParameter("username", user.getUsername());
Doing this resulted in the exact SQL I had in mind, which is an additional clause in the JOIN criteria with a variable:
SELECT
...
FROM
parent this_
LEFT OUTER JOIN
child child_ ON this_.parentId = child_.parentId
AND child_.username = ?
I might not have been clear about what end result I was looking for in my original question. Posting this answer in case it helps someone else.