Hibernate Join using criteria and restrictions - java

I have 2 entities as
PayoutHeader.java
#Entity
public class PayoutHeader extends GenericDomain implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private Integer month;
#Column
private Integer year;
#OneToOne
private Bank bank;
#Column
private Double tdsPercentage;
#Temporal(javax.persistence.TemporalType.DATE)
private Date **chequeIssuedDate**;
#Temporal(javax.persistence.TemporalType.DATE)
private Date entryDate;
}
PayoutDetails .java
#Entity
public class PayoutDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private PayoutHeader payoutHeader;
#Column
private Double amount;
#Column
private String bankName;
#Temporal(javax.persistence.TemporalType.DATE)
private Date clearingDate;
#OneToOne
private Advisor advisor;
#Column
private Long **advisorId**;
}
I want to write query using Hibernate Criteria like
Select pd.* from PayoutDetails pd, PayoutHeader ph where pd.payoutheaderId = ph.id and pd.advisorId = 1 and and ph.chequeIssuedDate BETWEEN STR_TO_DATE('01-01-2011', '%d-%m-%Y') AND STR_TO_DATE('31-12-2011', '%d-%m-%Y') ";
I have written query like this
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.setFetchMode("PayoutHeader", FetchMode.JOIN)
.add(Restrictions.between("chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
But is giving error as
org.hibernate.QueryException: could not resolve property:
chequeIssuedDate of: org.commission.domain.payout.PayoutDetails
I think is is trying to find chequeIssuedDate field in PayoutDetails, but this field is in PayoutHeader. How to specify alias during join ?

The criteria.setFetchMode("PayoutHeader", FetchMode.JOIN) just specifies that you want to get the header by a join, and in this case is probably unneeded. It doesn't change which table is used in the restrictions. For that, you probably want to create an additional criteria or an alias, more or less as follows:
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.createCriteria("payoutHeader")
.add(Restrictions.between("chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
or (using an alias)
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.createAlias("payoutHeader", "header")
.add(Restrictions.between("header.chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
See the Hibernate docs on Criteria Queries for examples of this.
It's also likely not appropriate to convert the advisorId to a string, as it is in fact a Long and probably mapped to a number field in sql.
It's common to also not map something like this advisorId field at all if you map the advisor, and use a restriction based on the advisor field, similarly to the way this deals with the payoutHeader field.
I wouldn't worry about getting all the fields from the header, but it may behave a bit differently if you get the createCriteria version to work.

Related

Translate native sql to JPA code or JPQL and make it pageable and sortable

I have two Entities like Below:
#Entity
#Table(name="tb_sm_config")
class Config {
#Id
private Long id;
private String code;
private String name;
#Enumerated(EnumType.STRING)
private State state;
#JsonIgnore
#OneToMany(mappedBy = "config", fetch = FetchType.LAZY)
private List<ConfigItem> items;
}
#Entity
#Table(name="tb_sm_config_item")
class ConfigItem {
#Id
private Long id;
private String code;
private String name;
#Enumerated(EnumType.STRING)
private State state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "config_id", referencedColumnName = "id", foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Config config;
}
the State is an Enum, there are three states : VALID, INVALID, DELETED
Then I define a ConfigVO like below:
class ConfigVO {
private Long id;
private String code;
private String name;
private State state;
private Long validItemCount;
private Long invalidItemCount;
pirvate Long deletedItemCount;
}
I want to provide fuzzy query based on config.name, config.code and precise query based on config.state and return a list of ConfigVO with pageable and sortable.
I know native sql will like :
select a.id, a.code, a.name, a.state,
coalesce(b._valid, 0) validItemCount,
coalesce(b._invalid, 0) invalidItemCount,
coalesce(b._deleted, 0) deletedItemCount
from tb_sm_config a left join
(select config_id, sum(if(state = 'VALID', 1, 0)) _valid, sum(if(state = 'INVALID', 1, 0)) _invalid, sum(if(state = 'DELETED', 1, 0)) _deleted from tb_sm_config_item group by config_id) b
on a.id = b.config_id
where code like %:code% and name like %:name% and state = :state
But here are the problems I don't know how to deal with:
The frontend will not always pass the code, name, state to query, so these three parameters is nullable.
If I use the #Query(nativeQuery=true, value=xxxx) in the ConfigRepository interface method, I don't know how to deal with the null value and the paging and sorting.
Is that any possible to achieve this goal through JpaSpecificationExecutor interface, Example interface, or something else?
I want to query base on the code, name, state, sometimes they will be null and want to count the total number of individual configItem states and pageable and sortable.

How to depict joins with #Query annotation in Spring JPA Repository method

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;

efficiently loading collection of collections from database in hibernate

I have an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/

Hibernate mapping of internationalized database

I want to get international content from database based on locale provided in hibernate query. This is a question about hibernate mapping but please feel free to propose better database design if mine is wrong.
My DB design (simplified):
db design
So I have table with non translatable data and additional table with translated content but with additional field "locale" for distinction of language.
My java classes looks like this:
public class Car {
private Long id;
private Long length;
private Long weight;
private CarTranslated carTranslated;
// getters and setters
public class CarTranslated {
private Long id;
private Long carId;
private String desc;
// getters and setters
I want to be able to get one car with single query. With regular jdbc I would use something like this sql query:
public Car getById(Long id, Locale locale) {
Car c = new Car();
String sql = "select c.car_id, c.length, c.weight, ct.id, ct.descryption,
ct.car_id as "Translated car_id" from car c join car_translated ct on
(c.car_id = ct.car_id) where c.car_id ="+ id+" and ct.locale ='"+locale+"'";
// code to set fields of the object using ResultSet
return c;
}
What would be a hibernate annotation mapping and query for this setup? I tried several attempts but to no avail. Currently my best attempt was as below:
Mapping:
#Entity
#Table(name="CAR")
public class Car {
#Id
#Column(name="car_id")
private Long carId;
#Column (name="weight")
private Long carWeight;
#Column (name="length")
private Long carLength;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name ="CAR_ID")
private CarTranslated localized;
// getters and setters
#Entity
#Table(name="CAR_TRANSLATED")
public class CarTranslated {
#Id
#Column (name="id")
private Long id;
#Column (name="car_id")
private Long carId;
#Column (name="descryption")
private String desc;
#Column(name="locale")
private Locale locale;
DAO:
public Car getCarById(Locale locale, Long id) {
Car car = new Car();
try {
Session session = HibernateUtils.getSessionFactory().openSession();
Criteria cr = session.createCriteria(Car.class)
.add(Restrictions.eq("carId", id));
Criteria cr1 = session.createCriteria(CarTranslated.class)
.add(Restrictions.eq("locale", locale));
car = (Car) cr.uniqueResult();
car.setLocalized((CarTranslated) cr1.uniqueResult());
} catch (Exception e) {
System.out.println(e.getMessage());
}
return car;
}
This is a work-around and I'm wondering what would be a proper way to do this?
You should have an annotation on both columns when mapping to a FK. (JavaDoc)

JPA Join using arbitrary field (not primary key)

I've got two entities that I want to join together using a field they have in common, called shared_id. The field is not the primary key of either entity. The shared_id is unique - each Hipster will have a unique shared_id.
The tables look like:
Hipster Fixie
========= ========
id id
shared_id shared_id
There is a OneToMany relationship between Hipsters and their Fixies. I've tried something like this:
#Entity
public class Hipster {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "shared_id")
private Integer sharedId;
#OneToMany(mappedBy = "hipster")
private List<Fixie> fixies;
}
#Entity
public class Fixie {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "shared_id", referencedColumnName = "shared_id")
private Hipster hipster;
}
#Repository
public class HipsterDAO {
#PersistenceContext
private EntityManager entityManager;
public Hipster getHipsterBySharedId(Integer sharedId) {
String queryString = "SELECT h FROM Hipster h WHERE h.sharedId = :sharedId";
TypedQuery<Hipster> query = entityManager.createQuery(queryString, Hipster.class);
query.setParameter("sharedId", sharedId);
try {
return query.getSingleResult();
} catch (PersistenceException e) {
return null;
}
}
}
Now, my DAO gives me this error:
java.lang.IllegalArgumentException: Can not set java.lang.Integer field Hipster.sharedId to java.lang.Integer
I think it's upset because the sharedId field is used in a relation, rather than just being a basic field. I haven't included the sharedId field in the Fixie entity, but I get the same result if I do. How do I persuade it to run this query for me? Do I need to change the query or the entities?

Categories

Resources