Hibernate/SQL: Selecting records only if exist in one-to-many relationship, returns too many - sql

In my app, I have 2 tables, Settlement Result and Settlement State
Settlement Result contains some basic data like name, type etc.
SettlementState is the "many" side of relatioship with Settlement Result and consist of Settlement Result PK and Status as Many-To-One ID as PK and a date. Example data:
Settlement Result
------------------------------
ID| Name | Sth
------------------------------
1 | Some Name | Something more
2 | Name2 | more2
Settlement State
----------
Result's ID | StatusId | Date
------------------------------
1 | 1 | some date
1 | 2 | date
1 | 3 | date
2 | 1 | date
Now I wish to select with HQL/Plain SQL that rows from Settlement Result, that have for example Status Id == 3, but not any with higher ID.
There are few possible statuses:
Status ID | Desc
-----------------
1 | Created
2 | Confirmed
3 | Accepted
4 | Rejected
When we are creating SettlementResult, there's always some "workflow". Firstly, Result has status Created (ID 1). Then it can be Rejected(ID 4) or Confirmed (ID 2). Then again we can Accept it or Reject.
So one SettlementResult can have SettlementStates with Status ID of 1,2,4 (Created, Confirmed, but not Accepted=Rejected).
The problem is, that I want to select only those SettlementResults which have certain status (for examle Created)."
With HQL query like this:
Query query = session.createSQLQuery( "select distinct s from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
It returns every Settlement Result, even those with Statuses 1,2,3 (cause collection contains the one with ID equal to created).
Is it possible to select ONLY those Settlement Results which have ONLY certain status for example if we want Created[ID 1], we get all with Settlement State status of 1 only, without 2,3 or 4 status. When we choose to select those with status id 3, we can accept if it has Settlement State with status id = 1,2,3 but NOT 4. Some kind of max[status.id]?
#Entity
#Table(name = "SETTLEMENT_STATE")
public class SettlementState
{
#EmbeddedId
private SettlementStatePK settlementStatePK;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "STATUS_DTTM")
private Date statusDate;
}
#Embeddable
public class SettlementStatePK implements Serializable
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SETTLEMENT_RESULT_ID")
private SettlementResult settlementResult;
}
#Entity
#Table(name = "SETTLEMENT_RESULT")
public class SettlementResult implements Serializable
{
#Id
#Column(name = "SETTLEMENT_RESULT_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MODEL_GROUP_ID")
private SettlementModelGroup settlementModelGroup;
#Column(name = "\"MODE\"")
private Integer mode;
#Column(name = "CREATED_DTTM")
private Date createdDate;
#Column(name = "DESCRIPTION")
private String description;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<SettlementState> settlementStates;
}

You have two options here:
If you see your query you are fetching SettlementResult objects
Query query = session.createSQLQuery( "select distinct s from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
Your query is perfect but once you have parent object SettlementResult and you access OneToMany collection settlementStates, hibernate will load all of them on the basis of SettlementResult P.K. (This is what you have observed as well).
So Option-1:
Go through Child to Parent
This means return SettlementState objects from your query as:
Query query = session.createSQLQuery( "select distinct states from SettlementResult s join s.settlementStates states where states.settlementStatePK.status.statusId == 1" );
And then you can access SettlementResult from state object, because you have defined ManyToOne for SettlementResult in SettlementState class. In this case you will definitely have those SettlementResult objects what you are looking for.
Option-2: Divide your SettlementState objects
Option 1 will work for you but this solution might seem odd to you. So the best way to resolve this problem is you can divide Settlement State objects (as described in your problem)
1. RejectedSettlementState
2. NonRejectedSettlementState
These two classes will extend one base abstract class (SettlementState). You can define discriminator formula on status id. Once you have these classes then you can associate these subclasses into SettlementResult. Here are classes you need (sudo)
#DiscriminatorForumla("case when status_id == 4 then "REJECTEDSTATE" else "NONREJECTEDSTATE" end")
public abstract class SettlementState{
...
// Define all of your common fields in this parent class
}
#DiscriminatorValue("REJECTEDSTATE")
public class RejectedSettlementState extends SettlementState{
...
// define only fields specific to this rejected state
}
#DiscriminatorValue("NOTREJECTEDSTATE")
public class NonRejectedSettlementState extends SettlementState{
...
// define only fields specific to this non-rejected state
}
Now the SettlementResult class
public class SettlementResult{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<RejectedSettlementState> rejectedSettlementStates;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "settlementStatePK.settlementResult")
private List<NonRejectedSettlementState> nonRejectedSettlementStates;
}
So once you have all these objects. Then you don't need query on status. You simply load the parent object SettlementResult and then access your rejected or non-rejected settlement states. Hibernate will use formula condition to initialize these collections using lazy load as defined in SettlementResult class.
Note
Both solutions to me are acceptable, it depends which one you will like in your system to be there. Second option gives you more edges for future.
Any more info: please ask! I have tried my best to get you through this idea :). Good Luck!

Related

Subquery - JPA - Include Subquery as part of the main selection

I have an issue to add the result of a subquery as part of the selection.
This is how my data model looks like:
#Entity
#Table(name = "flow")
public class Flow {
#Id
Long id;
#OneToMany(mappedBy="flow", fetch = FetchType.LAZY)
Set<Subscription> subscribers;
... other internal fields
//extra fields that come from other tables
#Transient //as it is not part of the model sometimes must be null
Long numberOfActiveSubscribers;
}
#Entity
#Table(name = "subscriptions")
public class Subscription {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "idflow")
Flow flow;
#OneToMany(name = "user")
User user;
#Column(name = "active")
boolean active;
}
I need to implement a query using jpa specifications that will include in its definition the number of user that have active subscriptions to the flow. So that I will be able to use pagination over that, including sort by this field that is not part of the original flow table. The SQL query I came out with look like this:
SELECT f.*,
(SELECT count(sf.id)
FROM subscription sf
WHERE sf.active = true
AND sf.idf = f.id) as numberofactivesubscribers,
FROM flow f;
I would like to us it in a findAll method like so:
this.repository.findAll(new Specification<Flow>() {
#Override
public Predicate toPredicate(Root<Flow> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Subquery<Long> sq = query.subquery(Long.class);
Root<Subscription> subsRoot = sq.from(Subscription.class);
sq.select(cb.count(subsRoot.get("id")))
.where(cb.equal(subsRoot.get("flow"), flowRoot),
cb.equal(subsRoot.get("active"), true));
//I DONT KNOW HOW TO INCLUDE THIS AS A PART OF THE MAIN SELECT OF THIS QUERY
}
}, pagination);
But as you can see I don't know how to include the subquery as part of the select method as I did in my SQL query.

Hibernate createNativeQuery returns duplicate rows

I have 2 database tables Customer and Items with 1 -> many relation. To fetch data from database i am using the following query.
select customer.id, customer.name, items.itemName, items.itemPrice from testdb.customer INNER JOIN items ON items.customer_Id = customer.id
I have an entity class Customers
#Entity
public class Customer{
#Id
private int id;
#Column
private String name;
#Column
private String itemName;
#Column
private int itemPrice;
public Customer() {}
//Getter and setter are here
.......
}
in Service class i have the following code.
#GET #Path("/getCustomerInfo")
#Produces(MediaType.APPLICATION_JSON)
public List getCustomerInfo() {
CustomerDao dao = new CustomerDao();
return dao.getBuildingsCustomerInfo();
}
in my DAO class i have the following code
public List<Customer> getCustomerInfo(){
Session session = SessionUtil.getSession();
String queryString = "the above mentioned query";
List<Customer> customerInfo = session.createNativeQuery(queryString, Customer.class) ;
session.close();
return customerInfo;
}
I am getting the following JSON response from the service
[id:1, name:"Alfred", itemName:"jeans", itemprice:10],[id:1, name:"Alfred", itemName:"jeans", itemprice:10],[id:2, name:"James", itemName:"watch", itemPrice:20 ],[id:2, name:"James", itemName:"watch", itemPrice:20 ], [id:2, name:"James", itemName:"watch", itemPrice:20 ]
The number of results are 5 which is correct But 2nd result is a copy of 1st, 4th and 5th are copies of 3rd. In 2nd, 4th and 5th results the itemName and the itemPrice should be different.
if I use createSQLQuery(queryString); instead of createNativeQuery(queryString, Customer.class); I am getting the correct result but without entity attribut names.
[1, "Alfred", "jeans", 10],[1, "Alfred", "shirt", 15],[2, "James", "watch", 20], [2, "James", "coffee", 25], [2, "James", "drinks", 30]
I have seen number of articles but could not find the solution. I have to use createNativeQuery() not createSQLQuery() because I need to map the entity class attributes. Please let me know if i am doing something wrong.
Your data structure is wrong on the Java side and not corresponding to the database relation. In the relation you describe you need to have a list of items:
#Entity
public class Customer implements Serializable {
// ... the fields you have so far
// assuming the parent field on the other side is called customer
// you may also want to set the cascade and orphanRemoval properties of the annotation
#OneToMany(mappedBy = "customer")
#JsonManagedReference // assuming you're using Jackson databind JSON
private List<Item> items;
}
And on the Item side:
#Entity
public class Item implements Serializable {
#Id
private int id;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_Id")
private Customer customer;
}
Then if you really the JSON data strucutred that way, you need a third Entity class to use as a ResultSetMapping.
#Entity
#SqlResultSetMapping(
name = "CustomerItem",
entities = #EntityResult(entityClass = CustomerItem.class)
)
#NamedNativeQueries({
#NamedNativeQuery(
name = "CustomerItem.getAll",
resultSetMapping = "CustomerItem"
query = "select customer.id as cid, items.id as iid, customer.name,"
+ " items.itemName, items.itemPrice from testdb.customer INNER JOIN"
+ " items ON items.customer_Id = customer.id"
)
})
public class CustomerItem implements Serializable {
#Id
private int cid;
#Id
private int iid;
#Column
private String name;
#Column
private String itemName;
#Column
private int itemPrice;
... getters and setters
}
Then you can use the native query in named variant, which should offer some slight optimizations.
List<CustomerItem> lst = em.createNamedQuery("CustomerItem.getAll", CustomerItem.class)
.getResultList();
The use of #SqlResultSetMapping is so that the returned entities are not monitored for changes, but you can still use the defined entity for the result. I believe that by JPA specification it should also work without it, but in Hibernate it doesn't. Could be a bug, or a planned, but not implemented feature, or I could just be misinterpreting the JPA usage, but this workaround does work with Hibernate 5+.
Not sure about the exact reason behind duplicates but SELECT DISTINCT will solve your issue as it will take only distinct records.
Refer using-distinct-in-jpa
I solve this issue by using #SqlResultSetMapping

Most elegant solution for internationalization with JPA / Hibernate?

Dear Stackflow Community,
I wish to store internationalized strings in my entities, and I am struggling with how to approach this. I will describe here the two solutions which I can think of, and would appreciate to receive your feedback.
Solution 1: Externalized from entities
In this solution I have the following table in my database:
LabelId Language VALUE Entity Id
WELCOME_TEXT EN "..." Questionnaire 1
WELCOME_TEXT DE "..." Questionnaire 1
GOODBYE_TEXT EN "..." Questionnaire 1
GOODBYE_TEXT DE "..." Questionnaire 1
QUESTION_TITLE EN "..." Question 12
QUESTION_TITLE DE "..." Question 12
OPTION_NAME EN "..." Option 23
OPTION_NAME DE "..." Option 23
FACTOR_NAME EN "..." Factor 11
FACTOR_NAME DE "..." Factor 11
I would access this collection always with the following methods:
void setLabels(Entity entity, LabelId labelId, Map<String, String langValues)
Map<String, String> getLabels(Entity entity, LabelId labelId)
Solution 2: With collection of elements
In this solution, all entities have Map stored in the same table:
LabelId Language VALUE questionnaire_id factor_id question_id option_id
WELCOME_TEXT EN "..." 1
WELCOME_TEXT DE "..." 1
GOODBYE_TEXT EN "..." 1
GOODBYE_TEXT DE "..." 1
QUESTION_TITLE EN "..." 12
QUESTION_TITLE DE "..." 12
OPTION_NAME EN "..." 23
OPTION_NAME DE "..." 23
FACTOR_NAME EN "..." 11
FACTOR_NAME DE "..." 11
Then, I would map all the fields in my entities in the following format:
i18n.java:
#Entity
#Table(uniqueConstraints={#UniqueConstraint(columnNames={"label", "language", "questionnaire_id", "question_id", ... })})
public class i18n extends PersistentObject { // where it gets ID from
Label label; // enum
Language language; // enum
String value; // the actual text
// bi-directional links to the linked entities
#ManyToOne Questionnaire questionnaire;
#ManyToOne Question question;
...
}
Questionnaire.java:
#OneToMany(mappedBy = "questionnaire")
List<i18n> labels;
Question.java:
#OneToMany(mappedBy = "question")
List<i18n> labels;
Solution 3: The non-solution
Another simple way to do this would be to simply store a map like this for every property:
#ElementCollection
Map<String, String> welcomeText;
#ElementCollection
Map<String, String> goodbyeText;
...
This solution creates A TON of tables in my database, which will make it messy to query and give maintenance.
Conclusions
The first solution gives me a better table, at the cost of having to retrieve the labels via an additional service, every time I need them.
The second solution keeps the code of the entities clean, but the SQL table messy, with many columns for all entities that will need translation.
How would you approach this? Is there a better solution?
Thanks in advance for all the feedback!
We solved this problem by seperating the translation for each entity and keeping normalisation, so therefore excluding the translation from the main entity, so for one example entitity of yours:
CREATE TABLE question(
id SERIAL PRIMARY KEY,
origin_date DATE; --Put all fields that don't change via language here
);
-- Create a translation table for the above entity and put all fields that
-- change through internationalization here for example name and description
CREATE TABLE question_translation(
id SERIAL PRIMARY KEY,
question_id int NOT NULL REFERENCES question(id),
language_id int NOT NULL REFERENCES language(id), --we have a separate table for languages so normalisation is provided
name TEXT,
description TEXT,
UNIQUE(question_id , language_id)
);
Example data for both tables would now be:
Table language (for example):
id iso_a2_code
------------------
5 DE
12 EN
Table question:
id date
----------------
1 19.04.2018
Table question_translation:
id question_id language_id name description
---------------------------------------------------------
1 1 5 TestDE Description in DE
2 1 12 TestEN Description in EN
JavaCode for Entities (not tested):
Question:
#Entity
#Table(name = "question")
public class Question {
#Id
#Column
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
#OneToMany(mappedBy = "question", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<QuestionTranslation> questionTranslations = new HashSet<>();
#Column
#Convert(converter = LocalDateConverter.class)
private LocalDate date;
}
Question Translation:
#Entity
#Table(name = "question_translation")
public class QuestionTranslation {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String description;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "question_id")
private Question question;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH)
#JoinColumn(name = "language_id")
private LanguageEntity language;
}
I solved the issue with the first approach described.
Additionally, I cached the queries with PreparedStatements and they perform really well. In addition to set single translations, I also created bulk editing and bulk retrieving methods, which performed even better.
Here is my solution - https://github.com/azerphoenix/spring-i18n-demo
You can take a look to this demo project.

Get record from another table using JPA

I have been trying to figure out how to do this for sometime without any luck and have not managed to find anything useful while search on Google either.
I have THREE tables:
HOTEL
- id
- name
- local_id (foreign key)
DESCRIPTION
- id
- description
- hotel_id (foreign key)
- locale_id (foreign key)
LOCALE
- id
- local
I also have the following HOTEL DAO model:
#Entity
#Table(name = "HOTEL")
public class Hotel implements Serializable {
#Column(name = "id")
private long id;
#Column(name = "description")
private HotelDescription description;
}
Using JPA, how can I retrieve the data from table DESCRIPTION based on hotel_id and locale_id to populate description in DAO model hotel?
Well, you also have HotelDescription JPA entity, right? So you can define bidirectional mapping for entities.
instead of
#Column(name = "description")
private HotelDescription description;
you should have something like
#OneToOne(mappedBy = "hotel", cascade = CascadeType.ALL)
private HotelDescription desc;
and on the other side, in HotelDescription you should have back mapping
#OneToOne
#JoinColumn(name = "hotel_id")
private Hotel hotel;
When you will extract Hotel entity, JPA will also fetch child entity (HotelDescription) for you.
if you want to use #OneToMany mapping it will be (many descriptions for one hotel)
#OneToMany(mappedBy = "hotel", cascade = CascadeType.ALL)
private HotelDescription desc;
and on the other side
#ManyToOne
#JoinColumn(name = "hotel_id")
private Hotel hotel;
In JPA you can use several types of mapping like OneToMany, ManyToMany... That's only basics. Find a tutorial. You may start here: http://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html (not the best one probably)
Oh. And make sure you annotate id with #Id
I would consider ditching the Locale table and working with java.util.Locale directly. Hibernate (not sure about other JPA implementations) has auto type conversion from char column to java.util.Locale. This would then look something like:
DESCRIPTION
- id
- description
- hotel_id (foreign key)
- locale
And Entity:
import java.util.Locale;
#Entity
#Table(name = "HOTEL")
public class Hotel implements Serializable {
#Column(name = "id")
private long id;
#OneToMany
#JoinColumn(name = "holiday_id", nullable = false)
#MapKeyColumn(name = "locale_id")
private Map<Locale, HotelDescription> descriptions;
public String getDescriptionForLocale(Locale locale){
//try an exact match e.g. en_us
if(descriptions.containsKey(locale){
return descriptions.get(locale).getDescription();
}
//try language only e.g. en
else if (decsriptions.containsKey(locale.getLanguage){
return descriptions.get(locale.getlanguage()).getDescription();
}
//return a default or null
return ??
}
}

Hibernate Multiple Row Set Differing In only one column?

How do I represent similar multiple row data in one hibernate pojo?
For example lets have a table say
PKEY | REFS | UNIQUEID
Now I envision a scenario in which I expect multiple REFS for same set of {PKEY, UNIQUEID} values. This will cause multiple rows in the database with same PKEY and UNIQUEID but differing REFS.
In such scenario how do I map that table to my hibernate pojo. Will the following work?
#Entity
#Table(name = "MYTABLE")
public class TablePojo
{
#Id
#Column(name = "PKEY")
int pkey;
#Column(name = "REFS")
List<String> refsList;
#Column(name = "USERID")
Long userId;
..........
..........
..........
};