JPQL - JOIN query with filter - java

I have the entitys:
First
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Technic implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String gosNumber;
private String invNumber;
private String shassisNumber;
private String engineNumber;
#Column(length = 100)
private String yearOfMake;
#ManyToOne
private Farm farm;
#JsonManagedReference
#ManyToOne
private TechGroup techGroup;
#JsonManagedReference
#ManyToOne
private TechType techType;
#JsonManagedReference
#ManyToOne
private TechMark techMark;
#JsonIgnore
#CreationTimestamp
#Column(name = "creation_date", updatable = false)
private LocalDateTime createdDate;
#JsonIgnore
#Column(name = "updated_date")
#UpdateTimestamp
private LocalDateTime updatedDate;
#JsonIgnore
#Column(columnDefinition = "Bool default false")
private Boolean isDel;
#JsonManagedReference
#OneToMany(mappedBy = "technic")
private List<TechnicStatus> technicStatusList = new ArrayList<>();
public List<TechnicStatus> getTechnicStatusList() {
return technicStatusList;
}
public void setTechnicStatus(TechnicStatus technicStatus) {
this.technicStatusList = new ArrayList<>();
this.technicStatusList.add(technicStatus);
}
Second:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class TechnicStatus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "technic_status_id")
private Long id;
#JsonBackReference
#ManyToOne
private Technic technic;
#JsonManagedReference
#ManyToOne
private Status status;
private Boolean isGarantia;
private Boolean isLizing;
private LocalDate visitedDate;
private LocalDate notWorkDate;
private String description;
#JsonIgnore
private boolean isActive;
#JsonIgnore
#CreationTimestamp
#Column(name = "creation_date", updatable = false)
private LocalDateTime createdDate;
}
I want to get result from my db which contains the List in each object Technic i have List technicStatusList = new ArrayList<>() in which i want to have a TechnicStatus only with value is isActive=true.
For this i right same JPQL query:
TypedQuery<Technic> query = em.createQuery("Select t from Technic t join TechnicStatus ts on t.id = ts.technic.id where t.isDel=false and ts.isActive=true and t.farm.id=:farmId order by t.techGroup.name, t.techType.name, t.techMark.name", Technic.class);
But get a result containing TechnicStatus, which returns a TechnicStatus with true and false (TechnicStatus.isActive=true, TechnicStatus.isActive=false).
I want to get result as this native query:
SELECT
*
FROM
technic
JOIN
technic_status ON technic.id = technic_status.technic_id
WHERE
technic.is_del = FALSE
AND technic_status.is_active = TRUE
AND technic.farm_id = 1722

The List of TechnicalStatus associated with a Technic will always be the complete list as defined by your mappings.
Essentially then you have 2 options. If you are only ever interested in TechnicalStatus with a status of Active then you can use the non-portable, Hibernate specific #Where clause on the association.
#JsonManagedReference
#OneToMany(mappedBy = "technic")
#Where("active = 1")
private List<TechnicStatus> technicStatusList = new ArrayList<>();
https://dzone.com/articles/hibernate-where-clause
Otherwise all you do is return a List of TechnicalStatus from query method which is not what you want but is all you have.

Its not possible to filter related collection in query conditions. You can get it by doing select on TechnicalStatus: select ts from TechnicStatus ts join Technic t where ...
Other thing I noticed:
You are overwriting list of existing statuses when adding new one:
public void setTechnicStatus(TechnicStatus technicStatus) {
this.technicStatusList = new ArrayList<>();
this.technicStatusList.add(technicStatus);
}
Initialize technicStatusList in field declaration. Method in Technic for adding:
public void addTechnicStatus(TechnicStatus technicStatus) {
getTechnicStatusList().add(technicStatus);
technicStatus.setTechnic(this);
}
Other thing I noticed:
When using join don't use on t.id = ts.technic.id. JPA will create correct native SQL just when you write: join TechnicStatus ts WHERE ...

Using join fetch should solve the issue, this would force the query to run eagerly all at once and only bring back the records that match the where clause.
so your query would be:
TypedQuery<Technic> query = em.createQuery("Select t from Technic t join fetch TechnicStatus ts where t.isDel=false and ts.isActive=true and t.farm.id=:farmId order by t.techGroup.name, t.techType.name, t.techMark.name", Technic.class);

Related

I don't now why occur N+1 Problem using QueryDSL

I used Spring Boot and QueryDSL.
When called findAllByWriterGroupByClient method in ClientMemoRepositoryImpl.java, [generated query 1] generated once and [generated query 2] generated several times.
Additionally, when read result of this query as Tuple in ClientMemoServiceImpl.java, [generated query 3] is generated many times.
ClientMemoRepositoryImpl.java
#Override
public List<Tuple> findAllByWriterGroupByClient(String searchKeyword, Long writerId, boolean hasAdminRole) {
QClientMemo qClientMemo1 = new QClientMemo("cm1");
QClientMemo qClientMemo2 = new QClientMemo("cm2");
JPAQuery<Tuple> memoDtoJPAQuery = qf.select(
JPAExpressions.selectFrom(qClientMemo1)
.where(qClientMemo1.clientId.eq(qClientMemo.clientId).and(
qClientMemo1.createdDate.eq(
JPAExpressions
.select(qClientMemo2.createdDate.max())
.from(qClientMemo2)
.where(qClientMemo2.clientId.eq(qClientMemo.clientId))
)
)
),
new CaseBuilder()
.when(qClientMemo.createdDate.gt(LocalDateTime.now().minusDays(7)))
.then(1)
.otherwise(0).sum()
)
.from(qClientMemo);
if ((!hasAdminRole) && writerId != null) {
memoDtoJPAQuery = memoDtoJPAQuery.where(qClientMemo.writer.id.eq(writerId));
}
if (searchKeyword != null)
memoDtoJPAQuery = memoDtoJPAQuery.where(
qClientMemo.title.contains(searchKeyword)
.or(qClientMemo.content.contains(searchKeyword))
.or(qClientMemo.clientId.clientName.contains(searchKeyword))
.or(qClientMemo.writer.name.contains(searchKeyword))
);
return memoDtoJPAQuery
.groupBy(qClientMemo.clientId)
.orderBy(OrderByNull.DEFAULT)
.fetch();
}
generated query 1
select
(select
clientmemo1_.id
from
client_memo clientmemo1_
where
clientmemo1_.client_id=clientmemo0_.client_id
and clientmemo1_.created_date=(
select
max(clientmemo2_.created_date)
from
client_memo clientmemo2_
where
clientmemo2_.client_id=clientmemo0_.client_id
)
) as col_0_0_, sum(case
when clientmemo0_.created_date>? then ?
else 0
end) as col_1_0_
from
client_memo clientmemo0_
group by
clientmemo0_.client_id
order by
null asc
generated query 2
select
[all fields of client_memo entity]
from
client_memo clientmemo0_
where
clientmemo0_.id=?
generated query 3
select
[all fields of client entity]
from
client client0_
where
client0_.id=?
ClientMemoServiceImpl.java
List<Tuple> clientMemos = clientMemoRepository.findAllByWriterGroupByClient(
readClientMemoDto.getSearchKeyword(),
readClientMemoDto.getUserId(),
hasAdminRole
);
clientMemos.forEach(clientMemo -> {
Map<String, Object> result = new HashMap<>();
Integer newCnt = clientMemo.get(1, Integer.class);
if (newCnt != null) {
result.put("newMemoNum", newCnt);
}
MemoDto memoDto = new MemoDto();
ClientMemo memo = clientMemo.get(0, ClientMemo.class);
if (memo != null) {
memoDto.ofClientMemo(memo);
result.put("memoDetail", memoDto);
}
results.add(result);
});
ClientMemo.java
#Entity
#Table(name = "client_memo")
#Getter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#DynamicInsert
public class ClientMemo {
#JsonIgnore
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "title", nullable = false)
private String title;
#Lob
#Column(name = "content")
private String content;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="client_id")
private Client clientId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="writer")
private User writer;
#Column(name = "created_date")
private LocalDateTime createdDate;
#Column(name = "updated_date")
private LocalDateTime updatedDate;
#Column(name = "is_admin")
private boolean isAdmin;
}
Client.java
#Entity
#Table(name = "client")
#Getter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#DynamicInsert
public class Client {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "client_name", nullable = false)
private String clientName;
#Column(name = "client_phone_num", nullable = false)
private String clientPhoneNum;
#Column(name = "service_start_time")
private LocalDateTime serviceStartTime;
#Column(name = "service_end_time")
private LocalDateTime serviceEndTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "media_id")
private Media media;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_charge_id")
private User personCharge;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "normal_memo")
private ClientMemo normalMemo;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "admin_memo")
private ClientMemo adminMemo;
#Column(name = "status", columnDefinition = "varchar(20) default 'UNCONTRACTED'")
#Enumerated(EnumType.STRING)
private ClientStatus status;
#Column(name = "is_deleted", nullable = false)
private boolean isDeleted;
}
All FetchType of Data Relationship are FetchType.LAZY.
I don't understand why occur this problem and why some people say that better using FetchType.LAZY than FetchType.EAGER.
Do I understand QueryDSL or SpringBoot correctly?
Thanks
You should share with us your Jpa entities.
In my opinion, you should have setted some associations in your entity (with #OneToMany, etc..), probably in Eager Mode (which is the default mode).
When you try to load one instance of your object from the database, Hibernate loads the associations as well. In eager mode, hibernate loads the associations by querying the database (which generates additional sql queries).
If you define your associations in Lazy mode, Hibernate will populate your entity jpa with some proxy objects and will fetch the associations later, only when you access it (so that means the sql query of your association is deffered when you only try to access the association in your code).

The Parent doensn't retrieve the childs hibertnate JPA

I have a Controller from rest service that I call a Hibernate method to get the result, but I really don't know why the children components didn't come. When I call this method using Junit, It works.
This is the Code:
{
#Entity
public class Product implements Serializable {
private static final long serialVersionUID = -6131311050358241535L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false)
private String name;
private String description;
#OneToMany(mappedBy = "product")
private List<Image> images = new ArrayList<Image>();
}
{
#Entity
public class Image implements Serializable {
private static final long serialVersionUID = 2128787860415180858L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#JoinColumn(name = "product_id")
#ManyToOne
private Product product;
private ImageType type;
}
{
#PersistenceContext
private EntityManager entityManager;
public List<Product> findAllWithParentProductsAndImage() {
String hpql = "select distinct p from Product p left join fetch p.images";
List<Product> resultList = entityManager.createQuery(hpql,
Product.class).getResultList();
return resultList;
}
}
By default #OneToMany will load lazily.
You should use #OneToMany( mappedBy = "product", fetch=FetchType.Eager ) to do Eager fetch
You can definitely use
#OneToMany(mappedBy = "product", fetch=FetchType.Eager)
However this has a downside. You will always be fetching children even if you only want the Parent and its few properties.
Use JOIN FETCH within your #Query if you are using JpaRepositories.
Check out the following related questions
How to properly express JPQL "join fetch" with "where" clause as JPA 2 CriteriaQuery?
http://www.objectdb.com/java/jpa/query/jpql/from#LEFT_OUTER_INNER_JOIN_FETCH_
https://stackoverflow.com/a/29667050/3094731

Join queries with JPQL in Spring Data Jpa

I created a left join query with JPQL in spring data jpa but failed in my unit test. There are two entities in the project.
Product entity:
#Entity
#Table(name = "t_goods")
public class Product implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "name", length = 20, nullable = false)
private String name;
#Column(name = "description")
private String desc;
#Column(name = "category", length = 20, nullable = false)
private String category;
#Column(name = "price", nullable = false)
private double price;
#Column(name = "is_onSale", nullable = false)
private Integer onSale;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "brand_id")
private Brand brand;
// getter and setter
}
Brand entity:
#Entity
#Table(name = "tdb_goods_brand")
public class Brand implements Serializable {
#Id
#GeneratedValue
#Column(name = "id", length = 6, nullable = false)
private Integer id;
#Column(name = "brand_name", unique = true, nullable = false)
private String name;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Product> products;
// getter and setter
}
And a third class Prod to map the query results to Object:
public class Prod implements Serializable {
private Integer id;
private String name;
private double price;
//private String brandName;
// getter and setter
}
It works fine with this query:
public interface ProductRepository extends JpaRepository<Product, Integer> {
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price) from Product p ")
Page<Prod> pageForProd(Pageable pageRequest);
}
But if I add new property brandName for Prod and refactor the query with left join, it test fails:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join com.pechen.domain.Brand b on p.brand_id = b.id")
Page<Prod> pageForProd(Pageable pageRequest);
The problem seems to be here on p.brand_id = b.id because there is not a brand_id property in Product, it's just a column name. So how can I make this work?
Update:
There turned to be some sytax errors in the JPQL query, just fix it as the following:
#Query(value = "select new com.pechen.domain.Prod(p.id, p.name, p.price, b.name) from Product p left join p.brand b")
Page<Prod> pageForProd(Pageable pageRequest);
Besides, it's very troublesome in this way to create another class everytime to map the query results into object(I mean the Prod class). So is there a good way to work with it? Any help would be appreciated.
Instead of p.brand_id = b.id you should do p.brand.id = b.id

How can I convert this 3 JOIN query into a Spring Data JPA named query method?

I am not so into Spring Data JPA and I have the following problem trying to implement a named query (the query defined by the method name).
I have these 3 entity classes:
#Entity
#Table(name = "room_tipology")
public class RoomTipology implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "tipology_name")
private String name;
#Column(name = "tipology_description")
private String description;
#Column(name = "time_stamp")
private Date timeStamp;
#OneToMany(mappedBy = "roomTipology")
private List<Room> rooms;
#OneToOne(mappedBy = "roomTipology")
private RoomRate roomRate;
// GETTER AND SETTER METHODS
}
That represents a tipology of room and that contains this field
#OneToMany(mappedBy = "roomTipology")
private List<Room> rooms;
So it contains the list of room associated to a specific room tipology, so I have this Room entity class:
#Entity
#Table(name = "room")
public class Room implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "id_accomodation_fk", nullable = false)
private Accomodation accomodation;
#ManyToOne
#JoinColumn(name = "id_room_tipology_fk", nullable = false)
private RoomTipology roomTipology;
#Column(name = "room_number")
private String number;
#Column(name = "room_name")
private String name;
#Column(name = "room_description")
#Type(type="text")
private String description;
#Column(name = "max_people")
private Integer maxPeople;
#Column(name = "is_enabled")
private Boolean isEnabled;
// GETTER AND SETTER METHODS
}
Representing a room of an accomodation, it contains this annoted field:
#ManyToOne
#JoinColumn(name = "id_accomodation_fk", nullable = false)
private Accomodation accomodation;
And finally the Accomodation entity class:
#Entity
#Table(name = "accomodation")
public class Accomodation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "accomodation")
private List<Room> rooms;
#Column(name = "accomodation_name")
private String name;
#Column(name = "description")
#Type(type="text")
private String description;
// GETTER AND SETTER METHODS
}
Ok, so now I have this Spring Data JPA repository class for RoomTipology:
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public interface RoomTipologyDAO extends JpaRepository<RoomTipology, Long> {
}
Here I want to define a named query method that return to me the list of all the RoomTipology object related to a specific accomodation, I have done it using SQL and it works fine:
SELECT *
FROM room_tipology as rt
JOIN room r
ON rt.id = r.id_room_tipology_fk
JOIN accomodation a
ON r.id_accomodation_fk = a.id
WHERE a.id = 7
But now I want to translate it in a named query method (or at least using HQL)
How can I do it?
Please Try:
#Repository
#Transactional(propagation = Propagation.MANDATORY)
public interface RoomTipologyDAO extends JpaRepository<RoomTipology, Long> {
List<RoomTipology> findByRooms_Accomodation(Accomodation accomodation);
}
The query builder mechanism built into Spring Data repository infrastructure is useful for building constraining queries over entities of the repository. The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it
At query creation time you already make sure that the parsed property is a property of the managed domain class. However, you can also define constraints by traversing nested properties.
Doc:Here

spring jpa custom query to return page<E>

I'm trying to get pagination from the list that I have using jpa repository. List is actually attribute of another object. It is, sort of, kind off, message board... Long story short, Class Tiket has a list of Poruka that I want to retrieve and paginate them. Here is what it looks like:
#Entity
#Table(name = "tiket")
public class Tiket implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="id")
private Long id;
#Column(name="naslov")
#NotEmpty(message= "Morate unijeti naslov tiketa")
private String naslov;
#Column(name = "tiket_datum")
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#NotNull
private Date tiketDatum;
#Column(name = "rijesen_datum")
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date rijesenDatum;
#ManyToOne(cascade = {CascadeType.REFRESH}, fetch=FetchType.EAGER )
#JoinColumn(nullable=false)
#NotNull
private Korisnik korisnik;
#OneToMany(cascade = {CascadeType.ALL}, fetch=FetchType.EAGER )
#GenericGenerator(name="uuid-gen", strategy = "increment")
#CollectionId(columns = #Column(name = "collection_id"), generator = "uuid-gen", type = #Type(type = "long"))
private List<Poruka> poruke = new ArrayList<Poruka>();
OK. List of Poruka is what I need.
So I tried creating custom query in PorukaRepository that would get the list of Poruka paginated by doing it like this:
public interface PorukaRepository extends JpaRepository<Poruka, Long> {
#Query("select t.poruke from Tiket t where t.id=?1")
Page<Poruka> findAllPoruke(Long tid, Pageable pageable);
}
and receive following error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') as col_0_0_ from tiket2 tiket2x0_, tiket2_poruka poruke1_, poruka poruka2_ whe' at line 1
then tried changing query, as the database has tiket_poruka table, to this:
#Query(value="SELECT * FROM poruka p where p.id IN (SELECT tp.poruke_id FROM tiket_poruka tp WHERE tp.tiket_id=?0)", nativeQuery = true)
Each time I receive following error during compilation:
No property find found for type ba.fit.vms.pojo.Poruka
then tried to put it in the TiketRepository... No luck. Then I added order by and sorting to my query, still no luck.
error this time is that it cannot convert from Tiket to Poruka
Of course, I can just retrieve the list and paginate it in my controller, but I want to learn and see if this is possible, as it seems logical. Do I need to create custom repository? Or just my query is wrong?
And here is the Poruka.class:
#Entity
#Table(name = "poruka")
public class Poruka implements Serializable{ //, Comparable<Poruka>
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name="id")
private Long id;
#Column(name="sadrzaj", length = 255)
#NotEmpty(message= "Sadrzaj ne moze biti prazan")
private String sadrzaj;
#Column(name = "datum")
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
#NotNull
private Date datum;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name="korisnik_id", nullable=true)
private Korisnik korisnik;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name="prethodna_id", nullable=true)
private Poruka prethodni;
Of course, classes are shown without getters and setters...

Categories