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

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' ");

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.

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?

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

#SqlResultSetMapping columns : entities with sub-entities

I have Test entity :
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "duration", nullable = false)
private int duration;
#Column(name = "test_name", nullable = false, unique = true)
private String testName;
#Column(name = "archived", nullable = false)
private boolean archived;
#OneToMany(mappedBy = "test", fetch = FetchType.EAGER)
private Set<Question> questions;
#ManyToMany(mappedBy = "tests")
private Set<User> users;
Question Entity:
public class Question {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "is_multichoice", nullable = false)
private boolean isMultichoice;
#Column(name = "is_open", nullable = false)
private boolean isOpen;
#Column(name = "picture")
private String picture;
#Column(name = "question")
private String question;
#ManyToOne
#JoinColumn(name = "test_id", nullable = false)
private Test test;
#Column(name = "archived", nullable = false)
private boolean isArchived;
#OneToMany(mappedBy = "question", fetch = FetchType.EAGER)
private Set<Answer> answers;
This Test entity has Set of questions, in such way Question Entity has Set of answers.
I wrote SQL query to get test (The reason why it is not HQL you could find by link Hibernate HQL : no entity found for query) :
#NamedNativeQuery(name = "getCurrentTestById",
query = "SELECT t.id as tId, t.test_name, t.duration, q.id as qId, " +
"q.question as question, q.is_multichoice as is_multichoice, " +
"q.is_open as is_open, a.id as aId, a.answer_text as answer_text FROM result r " +
"JOIN test t ON r.test_id = t.id " +
"JOIN user u ON r.user_id = u.id " +
"JOIN question q ON t.id = q.test_id JOIN answer a ON q.id = a.question_id " +
"WHERE t.id = :testId AND u.id = :userId AND r.permission = :permissionId " +
"AND q.archived = false AND a.archived = false")
Now i need to map it to my entity Test by using #SqlResultSetMapping annotation:
#SqlResultSetMappings({
#SqlResultSetMapping(name="toTest",
entities = {
#EntityResult(entityClass = com.bionic.entities.Test.class, fields = {
#FieldResult(name = "id", column = "tId"),
#FieldResult(name = "test_name", column = "test_name"),
#FieldResult(name = "duration", column = "duration"),
#FieldResult(name = "questions.question", column = "question"),
#FieldResult(name = "questions.id", column = "qId"),
#FieldResult(name = "questions.isMultichoice", column = "is_multichoice"),
#FieldResult(name = "questions.isOpen", column = "is_open"),
#FieldResult(name = "questions.answers.id", column = "aId"),
#FieldResult(name = "questions.answers.answer_text", column = "answer_text"),
})
})
})
I am getting exception :
Caused by: org.hibernate.MappingException: dotted notation reference neither a component nor a many/one to one
This is why frameworks are generally bad news. Instead of using hibernate, you should follow the interface segregation principle. Your application should not know or care about How to select the data you need and what the table names are etc. Simply create a stored procedure that takes this responsibility and call it as opposed to having all the junk code in your app. Then if you want an easy way to map just have your stored proc return json by calling For JSON at the end. Mapping object fields to the JSON object becomes a piece of cake. You will find that with frameworks you spend more time troubleshooting the framework than actually programming.

Categories