Hibernate Multiple Row Set Differing In only one column? - sql

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;
..........
..........
..........
};

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.

JPA/Hibernate overlapping PK and FK Columns

we're using Postgres and JPA/Hibernate to import a lot of data on a biweekly basis (~50-100M rows per import). We're trying to partition our tables per import, which has us running into some Hibernate PK/FK column mapping problems. The setup is essentially this on the SQL side
CREATE TABLE row (
import_timestamp timestamp,
id uuid,
PRIMARY KEY (import_timestamp, id)
) PARTITION BY LIST (import_timestamp);
CREATE TABLE row_detail (
import_timestamp timestamp,
id uuid,
row_id uuid,
PRIMARY KEY(import_timestamp, id),
CONSTRAINT row_detail_row_fk FOREIGN KEY (row_id, import_timestamp) REFERENCES row (id, import_timestamp)
) PARTITION BY LIST (import_timestamp);
and this on the Java side:
#Entity(name = "row")
public class RowEntity {
#EmbeddedId
private PartitionedId id;
#OneToMany(cascade = ALL, mappedBy = "row")
private List<RowDetailEntity> details;
}
#Entity(name = "row_detail")
public class RowDetailEntity {
#EmbeddedId
private PartitionedId id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "row_id", referencedColumnName = "id"),
#JoinColumn(name = "importTimestamp", referencedColumnName = "importTimestamp")
})
private RowEntity row;
}
#Embeddable
public class PartitionedId implements Serializable {
private Instant importTimestamp;
private UUID id;
}
Hibernate then complains on boot that:
column: import_timestamp (should be mapped with insert="false" update="false")
I can silence that error by doing as it says, but that makes little sense, because I am forced to set insertable=false and updatable=false for both #JoinColumn()s, which would mean row_id isn't populated on insert.
I could go the #MapsId route, but only if I give the row_detail table a PK that includes all 3 properties (import_timestamp, id, row_id), and I don't really want or need that.
So the question is, how do I get Hibernate to understand my overlapping, but not entirely nested PK/FK?

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

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/SQL: Selecting records only if exist in one-to-many relationship, returns too many

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!