Join queries with JPQL in Spring Data Jpa - java

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

Related

duplicate [ID] alias error for sql statement with join

I created a search button with a SQL query which is including JOIN 2 times, but its throwing error like:
org.hibernate.loader.NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias ID during auto-discovery of a
native-sql query
This is the Repository method:
#Query(value = "select * from reports r join patients p on r.patient_id = p.id join lab_technicians lt on r.lab_technician_id = lt.id where p.name like '%:patientName%' and lt.name like '%:labTechnicianName%' and p.identity_no like '%:patientIdentityNo%'", nativeQuery = true)
List<Report> findBySearch(#Param("patientName") String patientName, #Param("labTechnicianName") String labTechnicianName, #Param("patientIdentityNo") String patientIdentityNo);
To make you understand the project templates, these are the entities classes:
Person class:
#MappedSuperclass
public abstract class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
Patient class:
#Entity
#Table(name = "patients")
public class Patient extends Person{
#Column(name = "identity_no") private String identityNo;
#OneToMany(mappedBy = "patient") private List<Report> reports;
LabTechnician class:
#Entity
#Table(name = "lab_technicians")
public class LabTechnician extends Person{
#Column(name = "hospital_id")
private String hospitalId;
#OneToMany(mappedBy = "labTechnician")
private List<Report> reports;
and lastly Report class:
#Entity
#Table(name = "reports")
public class Report {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "lab_technician_id")
private LabTechnician labTechnician;
#ManyToOne
#JoinColumn(name = "patient_id")
private Patient patient;
#Column(name = "file_number")
private String fileNumber;
#Column(name = "diagnostic")
private String diagnostic;
#Column(name = "detail_of_diagnostic")
private String detailOfDiagnostic;
#Column(name = "date_of_report")
private Date dateOfReport;
I changed List in #OneToMany relationships to Set but its not changed anything
#Query(value = "select * from reports r join patients p on r.patient_id =
p.identity_no join lab_technicians lt on r.lab_technician_id =
lt.hospital_id where p.name like '%:patientName%' and lt.name like
'%:labTechnicianName%' and p.identity_no like '%:patientIdentityNo%'",
nativeQuery = true)
List<Report> findBySearch(#Param("patientName") String patientName,
#Param("labTechnicianName") String labTechnicianName,
#Param("patientIdentityNo") String patientIdentityNo);
Use this query it will work fine

JPQL TypedQuery - setParamer does not work

I am trying to fetch an entity which has a one-to-one relation using a named Query. My "where" condition is on the relation entity. I am giving a named parameter. When I execute the query it ignores the parameter passed and giving me all the records.
I tried with positional parameter, it too didn't work.
Query
#NamedQuery(
name = "country.by.region",
query = " select c from Country c join Region r on r.id = :regid"
)
Country Entity
public class Country {
#Id
#Column(name = "COUNTRY_ID")
private String id;
#Column(name = "COUNTRY_NAME")
private String name;
#OneToOne(targetEntity = Region.class, cascade = CascadeType.ALL)
#JoinColumn(name = "REGION_ID")
private Region region;
// ...
}
Region Entity
public class Region {
#Id
#Column(name = "REGION_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "regSeq")
private int id;
#Column(name = "REGION_NAME")
private String name;
// ...
}
DAO Impl
#Override
public List<Country> findBy(Region region) {
TypedQuery<Country> query = getEntityManager().createNamedQuery("country.by.region", Country.class);
query.setParameter("regid", region.getId());
query.setMaxResults(30);
return query.getResultList();
}
Try to correct your query in this way:
select c from Country c join c.region r where r.id = :regid
See also this section of the documentation.

How to write a spring boot jpa specification joining multiple tables

I want to write below query using spring boot specification.
SELECT o.*
from orders as o
inner join user as u on o.user_id = u.id
inner join user_group as ug on u.user_group_id = ug.id
left join order_product op on o.id = op.order_id
left join mobile_order_product mop on op.id = mop.order_product_id
left join mobile_device as md on mop.mobile_device_id = md.id
left join tablet_order_product top on op.id = top.order_product_id
left join tablet_device as td on top.tablet_device_id = td.id
where ug.id = 1
and (md.imei = 123456789 or td.imei = 123456789)
I try to write specification like below but I couldn't find a way to join order_product table.
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, int imei) {
return (root, query, cb) -> {
Join<Object, User> user = root.join("user");
Join<Object, UserGroup> userGroup = user.join("userGroup");
// how to join order_product and other join tables
Predicate equalPredicate = cb.equal(userGroup.get("id"), userGroupId);
return cb.and(equalPredicate);
};
}
I am going to put answer in my own question.
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private User user;
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderProduct> orderProducts ;
}
#Entity
#Table(name = "order_product")
public class OrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private Order order;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<MobileOrderProduct> mobileOrderProducts;
#OneToMany(mappedBy = "orderProduct", fetch = FetchType.LAZY)
private List<TabletOrderProduct> tabletOrderProducts;
}
#Entity
#Table(name = "mobile_order_product")
public class MobileOrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String mobileCode;
private String mobileNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "id", nullable = false)
#JsonIgnore
private MobileDevice mobileDevice;
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
#JoinColumn(referencedColumnName = "id", nullable = false)
private OrderProduct orderProduct;
}
#Entity
#Table(name = "mobile_device")
public class MobileDevice {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String serialNumber;
private String imei;
#OneToMany(mappedBy = "mobileDevice", fetch = FetchType.LAZY)
#JsonIgnore
private List<MobileOrderProduct> mobileOrderProducts;
}
Here I only included couple of my entity class because then you can understand the table structure correctly
public static Specification<Order> filterOrdersByGroupIdAndImei(int userGroupId, String imei) {
return (root, query, cb) -> {
List<Predicate> list = new ArrayList<Predicate>();
Join<Order, User> user = root.join("user");
Join<User, UserGroup> userGroup = user.join("userGroup");
Join<Order, OrderProduct> orderProduct = root.join("orderProducts", JoinType.INNER);
Join<OrderProduct, MobileDevice> mobileDevice = orderProduct
.join("mobileOrderProducts", JoinType.LEFT)
.join("mobileDevice", JoinType.LEFT);
Join<OrderProduct, TabletDevice> tabletDevice = orderProduct
.join("tabletOrderProducts", JoinType.LEFT)
.join("tabletDevice", JoinType.LEFT);
list.add(cb.equal(userGroup.get("id"), userGroupId));
list.add(cb.or(cb.equal(mobileDevice.get("imei"), imei), cb.equal(tabletDevice.get("imei"), imei)));
Predicate[] p = new Predicate[list.size()];
return cb.and(list.toArray(p));
}

Hibernate creates two join the same table

I have three tables: ScanMeta, Scan and Batch.
#Entity
#Table(name = "scan_meta_letter")
public class ScanMetaLetter {
#Id
#Column(name = "scan_meta_id")
private Long id;
#OneToOne(mappedBy = "scanMeta")
#JoinColumn(name = "scan_id")
private Scan scan;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
....
}
#Entity
#Table(name = "scan")
public class Scan {
#Id
#GeneratedValue
#Column(name = "scan_id")
private Long id;
#Column(name = "read_date")
private OffsetDateTime readDate;
#Column(name = "deletion_date")
private OffsetDateTime deletionDate;
#Column(name = "type", length = 10)
#Enumerated(EnumType.STRING)
private ScanType type;
#ManyToOne(fetch = FetchType.LAZY)
private Batch batch;
....
}
#Entity
#Table(name = "batch")
public class Batch {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "batch_status")
#Enumerated(EnumType.STRING)
private BatchStatus batchStatus;
#Column(name = "batch_type", length = 10)
#Enumerated(EnumType.STRING)
private BatchType batchType;
....
}
When I create Specification<ScanMettaLetter>, I fetch tables: Scan and Batch.
Specification<ScanMetaLetter> s = Specification.where((root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch("scan", JoinType.LEFT).fetch("batch", JoinType.LEFT);
return null;
});
If I would like the response to sort by scan.type - everything is ok. But if sort by - scan.batch.batchStatus, I get the error:
could not prepare statement; SQL [select distinct scanmetale0_.scan_meta_id as scan_met1_7_0_,
scan1_.scan_id as scan_id1_3_1_, batch2_.id as id1_0_2_, scanmetale0_.first_name as
recipien3_9_0_, scanmetale0_.last_name as recipien5_9_0_, scan1_.batch_id as
batch_i13_3_1_, scan1_.deletion_date as deletion3_3_1_, scan1_.read_date as read_dat8_3_1_,
scan1_.type as type11_3_1_, batch2_.batch_status as batch_st3_0_2_,
batch2_.batch_type as batch_ty4_0_2_ from
scan_meta_letter scanmetale0_ left outer join scan scan1_ on
scanmetale0_.scan_meta_id=scan1_.scan_id left outer join batch batch2_ on
scan1_.batch_id=batch2_.id cross join batch batch6_ where scan1_.batch_id=batch6_.id and
(scan1_.reg_code like ?) and scan1_.type=? and batch6_.batch_status=? and 1=1 order by
batch6_.batch_status asc limit ?]; nested exception is
org.hibernate.exception.SQLGrammarException: could not prepare statement
I see that two alias for batch is created -> batch2 and batch6. Why? Can I bypass this somehow?

jpql select object from table based on some property from another table?

I am new to jpql.
I have two mySql tables.
Table advert: with columns:
id, name, description, phone, category etc..
And table advert_property with following columns:
id int 11
advert_id int 11
name varchar 255
value varchar 255
descr varchar 255
My goal is to choose object from table advert which has a property category = "flats" written in table advert property with name number_rooms = "234" written in table advert_property.
I'm a little bit confused with jpql syntaxis i came to this solution:
Query q = em.createQuery("SELECT ap FROM AdvertProperty as ap, Advert as a "
+ " where a.category= 'flats' and ap.advertId = a.id and ap.name='number_rooms' ");
List<Advert> ads = q.getResultList();
But it doesn't work as i needed..
Please suggest,
Thanks
Advert entity :
public class Advert implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#Size(max = 255)
private String title;
#Lob
#Size(max = 65535)
private String content;
private Integer price;
#Size(max = 255)
#Column(name = "contact_person")
private String contactPerson;
// #Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")//if the field contains email address consider using this annotation to enforce field validation
#Size(max = 255)
private String email;
// #Pattern(regexp="^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$", message="Invalid phone/fax format, should be as xxx-xxx-xxxx")//if the field contains phone or fax number consider using this annotation to enforce field validation
#Size(max = 255)
private String phone;
#Column(name = "address_id")
private Integer addressId;
#Column(name = "category_id")
private Integer categoryId;
#Basic(optional = false)
#NotNull
#Column(name = "company_type")
private boolean companyType;
#Basic(optional = false)
#NotNull
private boolean approved;
#Column(name = "user_id")
private Integer userId;
#Column(name = "who_can_watch")
private Integer whoCanWatch;
#Basic(optional = false)
#NotNull
#Column(name = "creation_date")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#Size(max = 255)
private String razdel;
public Advert() {
}
public Advert(Integer id) {
this.id = id;
}
AdvertProperty Entity:
#Entity
#Table(name = "advert_property")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "AdvertProperty.findAll", query = "SELECT a FROM AdvertProperty a"),
#NamedQuery(name = "AdvertProperty.findById", query = "SELECT a FROM AdvertProperty a WHERE a.id = :id"),
#NamedQuery(name = "AdvertProperty.findByAdvertId", query = "SELECT a FROM AdvertProperty a WHERE a.advertId = :advertId"),
#NamedQuery(name = "AdvertProperty.findByName", query = "SELECT a FROM AdvertProperty a WHERE a.name = :name"),
#NamedQuery(name = "AdvertProperty.findByValue", query = "SELECT a FROM AdvertProperty a WHERE a.value = :value"),
#NamedQuery(name = "AdvertProperty.findByDescr", query = "SELECT a FROM AdvertProperty a WHERE a.descr = :descr")})
public class AdvertProperty implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#Column(name = "advert_id")
private Integer advertId;
#Size(max = 255)
private String name;
#Size(max = 255)
private String value;
#Size(max = 255)
private String descr;
depending on how you implemented your entities:
SELECT a FROM Advert a JOIN a.properties ap where a.category = 'flats' and ap.name='number_rooms'
or
SELECT a FROM AdvertPropery ap JOIN ap.advert where a.category = 'flats' and ap.name='number_rooms'
However post your entities to have an exact answer.
That's not how JPA is supposed to do a mapping, you should use relational annotations (#OneToMany, #ManyToOne, ...) and mapping annotations (#JoinColumn, #JoinTable, ...)
without relations (and indexes!!) the only query you can do is similar to what you've just done:
SELECT DISTINCT ap FROM AdvertProperty ap, Advert a where a.category= 'flats' and ap.advertId = a.id and ap.name='number_rooms'
nevertheless this query is not optimized (no indexes...) and has a very bad performace.
EDIT
Since you already have a foreign key from advert_property to advert, you can model your entities accordingly
In Advert entity
...
#OneToOne(mappedBy = "advert")
private AdvertProperty property;
// or this, in case it is one to many
#OneToMany(mappedBy = "advert")
private List<AdvertProperty> properties;
...
In AdvertProperty entity
#ManyToOne // or #OneToOne
#JoinColumn("advert_id")
private Advert advert;
In case it is #OneToMany, the query would look like this
Query q = em.createQuery("SELECT a FROM Advert a join a.properties p where a.category = 'flats' and p.name='number_rooms' ");
In case it is #OneToOne, the query would look like this
Query q = em.createQuery("SELECT a FROM Advert a where a.category = 'flats' and a.property.name='number_rooms' ");

Categories