Convert relational data into JSON Efficiently - java

I have following table People
I want to get relational records and make the JSON Array like following:
{ "Person":[
{"ID":1,
"FirstName":"James",
"LastName":"Donovan",
"Child":[
{"ID":6, "FirstName":"Nikolai", "LastName":"Donovan"}
]
},
{"ID":2,
"FirstName":"Jeffrey",
"LastName":"Williams",
"Child":[
{"ID":4, "FirstName":"Carol", "LastName":"Williams"},
{"ID":5, "FirstName":"Sarah", "LastName":"Williams"}
]
},
.... and so on
]
}
What I use following approach;
short summary of below code: I perform db operation twice in loop and put node into JSON Object is match certain conditions.
preparedStatement = con.prepareStatement("SELECT * FROM PEOPLE");
rs = preparedStatement.executeQuery(); // ResultSet Object
if (rs != null && rs.next()) {
Roles = new JSONObject();
parentNode = new JSONObject();
childNode = new JSONObject();
while(rs.next()) {
parentNode.put("ID", rs.getInt("Id"));
parentNode.put("FirstName", rs.getString("firstname"));
parentNode.put("LastName", rs.getString("lastname"));
if(rs.getInt("parent") > 0) { // Equal 0 Mean it has not child
// Perform again database operation and get record related to parent id
preparedStatement = con.prepareStatement("SELECT * FROM PEOPLE WHERE parent=?");
preparedStatement.setString(1, rs.getInt("id");
resultSet2 = preparedStatement.executeQuery();
while(resultSet2.next()) {
childNode.put("ID", rs.getInt("Id"));
childNode.put("FirstName", rs.getString("firstname"));
childNode.put("LastName", rs.getString("lastname"));
parentNode.put("Child":childRole); // put child into parent node
}
}
}
}
What I want?
Performing db select query in loop is too much expensive for server side.
Is there any better solution which generate desired JSON of relational data and save me from additional SELECT operations!
I am thankful for your attention!

If you are using mysql 5.7 you can you JSON_ARRAY function to create json array. or JSON_OBJECT https://dev.mysql.com/doc/refman/5.7/en/json-attribute-functions.html

maybe use two queries.
select blah where parent = 0.
select blah where parent != 0 order by parent, id
loop through the results of the second result set and assign them to the parent in the first result set.
use gson (or jackson or whatever json library you prefer) to convert from the object list to the final jason.
brief codelike stuff
select the parents (parent = 0).
for each parent row, add the row to a Map<String, Entity> where the key is the parent id.
select the children (parent != 0).
for each child row, get the parent row from the map using the parent id then add them to the parent entity.
use the same class for parent and child rows, initialize the child collection to null and create it on the first add of a child. This way, the child array will be included for parents that have children and not included for parents with no children and for children.

You could issue a single select
SELECT p.ID, p.FirstName, p.LastName, c.ID, c.FirstName, c.LastName
FROM PEOPLE p LEFT OUTER JOIN PEOPLE c ON c.parent = p.ID
ORDER BY p.ID
and then loop over the result.
Whenever the parent ID changes (and for the first result row) you build a new current parentNode. Then you read the child data (which may be empty in case the parent has no children) and put it into the child json array of the current parentNode.

Related

Hibernate - sqlQuery map redundant records while using JOIN on OneToMany

I have #OneToMany association between 2 entities (Entity1 To Entity2).
My sqlQueryString consists of next steps:
select ent1.*, ent2.differ_field from Entity1 as ent1 left outer join Entity2 as ent2 on ent1.item_id = ent2.item_id
Adding some subqueries and writing results to some_field2, some_field3 etc.
Execute:
Query sqlQuery = getCurrentSession().createSQLQuery(sqlQueryString)
.setResultTransformer(Transformers.aliasToBean(SomeDto.class));
List list = sqlQuery.list();
and
class SomeDto {
item_id;
some_filed1;
...
differ_field;
...
}
So the result is the List<SomeDto>
Fields which are highlighted with grey are the same.
So what I want is to group by, for example, item_id and
the List<Object> differFieldList would be as aggregation result.
class SomeDto {
...fields...
List<Object> differFieldList;
}
or something like that Map<SomeDto, List<Object>>
I can map it manually but there is a trouble:
When I use sqlQuery.setFirstResult(offset).setMaxResults(limit)
I retrieve limit count of records. But there are redundant rows. After merge I have less count actually.
Thanks in advance!
If you would like to store the query results in a collection of this class:
class SomeDto {
...fields...
List<Object> differFieldList;
}
When using sqlQuery.setFirstResult(offset).setMaxResults(n), the number of records being limited is based on the joined result set. After merging the number of records could be less than expected, and the data in List could also be incomplete.
To get the expected data set, the query needs to be broken down into two.
In first query you simply select data from Entity1
select * from Entity1
Query.setFirstResult(offset).setMaxResults(n) can be used here to limit the records you want to return. If fields from Entity2 needs to be used as condition in this query, you may use exists subquery to join to Entity2 and filter by Entity2 fields.
Once data is returned from the query, you can extract item_id and put them into a collection, and use the collection to query Entity 2:
select item_id, differ_field from Entity2 where item_id in (:itemid)
Query.setParameterList() can be used to set the item id collection returned from first query to the second query. Then you will need to manually map data returned from query 2 to data returned from query 1.
This seems verbose. If JPA #OneToMany mapping is configured between the 2 entity objects, and your query can be written in HQL (you said not possible in comment), you may let Hibernate lazy load Entity2 collection for you automatically, in which case the code can be much cleaner, but behind the scenes Hibernate may generate more query requests to DB while lazy loading the entity sitting at Many side.
The duplicated records are natural from a relational database perspective. To group projection according to Object Oriented principles, you can use a utility like this one:
public void visit(T object, EntityContext entityContext) {
Class<T> clazz = (Class<T>) object.getClass();
ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
boolean objectVisited = entityContext.isVisited(objectClassId);
if (!objectVisited) {
entityContext.visit(objectClassId, object);
}
P parent = getParent(object);
if (parent != null) {
Class<P> parentClass = (Class<P>) parent.getClass();
ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
if (!entityContext.isVisited(parentClassId)) {
setChildren(parent);
}
List<T> children = getChildren(parent);
if (!objectVisited) {
children.add(object);
}
}
}
The code is available on GitHub.

Hibernate result list cast to get rows

Hello I had problem with iterate Hibernate ResultList
I had followed query that I got from external class:
queryContent = "select distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And I must sum whole aab.paymentConditions.currency, check numbers of statutes and names.
I want to do this by iterate list of results:
Query q = session.createQuery(queryContent);
List result = q.list();
Long wholeCurrency, numberOfStatutes;
for(Object res : result){
//wholeCurrency += res.getColumnName?
}
My question is how to cast res Object to have possibility to get concrete column values? I had read about create map inside hibernate query but I don't know it is good practice to modyfied query string by adding
"new map(" prefix and then ")"
before from sql statement
Solution:
After All I decided to use map in my query. I modified my external query by adding hibernate map statement by replacing select by 'select new map(' and from by ') from'.
Additional thing is to add 'as' statement with name of key because without them column keys are integer.
So after all my query looks like follow:
"select new map( distinct c.identity, c.number, c.status as status, ctr.name as name, aab.paymentConditions.currency as currency ) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
That was the most siutable solution for me, I tried with 'NEW com.example.MyClass' as Kostja suggested but in my case I didn't have control for incoming query so I can not rely on pernament constructor.
new List( select...
Is also interest but it also didn't tell me on with position I have my field that give me information.
If I understand correctly, you want to have a typed representation of your result without it being an entity itself. For this, you can use constructor queries:
"SELECT NEW com.example.MyClass( e.name, e.data) FROM Entity e"
MyClass has to have a matching constructor. Full qualification (com.example) is not mandatory AFAIK.
If you are using this query often, creating a view in the DB may be a good idea. You can map a view to an entity just as if it were a regular table, but please note that you cannot store changes to you data over a mapped view.
EDIT: Turns out, mapping to an unspecified Map is alright with Hibernate:
select new map( mother as mother, offspr as offspr, mate as mate )
As per http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch11.html#ql-select-clause
you can use
queryContent = "select new list(distinct c.identity, c.number, c.status, ctr.name, aab.paymentConditions.currency) from AgreementStateBean ast join ast.currentAgreement aab join conagr.contract c where c.agreementStateId = ? and con.mainContractor = true ? "
And you get List<List> as result.

Using sub queries

I am trying to access a data in a table using a sub query.
The table 1 contains a foreign key to table 2 , which means i can use that key to access the data in table 2.
My problem is after i return the array list from the below shown method , the arraylist is null.
This is what i have done:
LogEntry logBookDates;
List<LogEntry> bookList =new ArrayList();
try{
PreparedStatement getSummaryStmt=con.prepareStatement("SELECT * FROM LOGENTRYTABLE WHERE DIARYCODE =(SELECT Diarycode FROM LOGBOOKTABLE WHERE STUDENTUSERNAME=? OR SUPERVISORUSERNAME=? AND PROJECT_APPROVE_STATUS=?)");
//the above statment is the sub query which i have created, i get the diary code from log book table and then access the log entry table.
getSummaryStmt.setString(1,userName);
getSummaryStmt.setString(2,userName);
getSummaryStmt.setString(3,"Accepted");
ResultSet rs=getSummaryStmt.executeQuery();
while(rs.next())
{
logBookDates=new LogEntry(rs.getString("STUDENTUSERNAME"),rs.getString("SupervisorUsername"),rs.getString("projecttitle"),rs.getString("projectDescription"),rs.getDate("startDate"),rs.getDate("enddate"),rs.getString("project_approve_status"),rs.getString("diarycode"),rs.getString("projectcode"),rs.getInt("Index"),rs.getString("log_Entry"),rs.getDate("logentry_date"),rs.getString("supervisor_comment"),rs.getString("project_progress"));
bookList.add(logBookDates);
}
}catch(Exception e){}
return bookList;
}
I have not used sub queries before and this is the first time am using them.
What seems to be the problem here ?
Thank you for your time.
Edit : Sample data of logbook table
Sample Data of logentry table
Expected output:
I don't have a screen shot of that but what i need is just to iterate through the arraylist which will be returned from the above method.
Here is the problem, the LOGENTRYTABLE table doesn't contain a column with STUDENTUSERNAME, SupervisorUsername, projecttitle, projectDescription, startDate, etc...
rs.getString("STUDENTUSERNAME"), rs.getString("SupervisorUsername"), etc...
probably, you need JOIN query
"SELECT * FROM LOGENTRYTABLE LT
INNER JOIN LOGBOOKTABLE LB ON LT.DIARYCODE=LB.DIARYCODE
WHERE LT.DIARYCODE =
(SELECT DIARYCODE FROM LOGBOOKTABLE
WHERE (STUDENTUSERNAME=? OR SUPERVISORUSERNAME=?)
AND PROJECT_APPROVE_STATUS=?)"

How to flatten the results of a Hibernate query

Say I executed a theoretical HQL query like FROM Customer. And in Customer is a getOrders() getter returning a ManyToOne collection of Order objects. This executes a SQL statement selecting from Customer with a left join to Order.
Through the object model, I can programmaticly iterate over Customers and then iterate over Orders.
However, I want to convert the hierarchical object model to a flat tabular result of the left join so that the results would look much like that of this SQL query:
SELECT *
FROM Customer
LEFT JOIN Order on Customer.customerId = Order.customerId
Sample result:
Customer.customerId ... Order.orderId Order.customerId ...
1 200 1
2 201 2
2 202 2
3 NULL NULL
Is there an easy way to do this with Hibernate?
Depends what you want at the "scalar" level which you control through using an explicitl select clause.
select c, o
from Customer c left join c.orders o
returns you List of (Customer, Order) tuples. Or:
select c.id, c.name, o.id, ...
from Customer c left join c.orders o
which returns you a scalar projection of the atomic pieces.
In both cases you get back a List. You can use "dynamic instantiation" in both cases (though really its more useful in the second case imho):
select new CustomerOrderSummary( c.id, c.name, o.id, ... )
from Customer c left join c.orders o
where CustomerOrderSummary is just a plain class with matching constructor.
Possible you can do this via expressing of result set. http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/query_native.html
From the example:
#SqlResultSetMapping(name="GetNightAndArea", entities={
#EntityResult(name="org.hibernate.test.annotations.query.Night", fields = {
#FieldResult(name="id", column="nid"),
#FieldResult(name="duration", column="night_duration"),
#FieldResult(name="date", column="night_date"),
#FieldResult(name="area", column="area_id")
}),
#EntityResult(name="org.hibernate.test.annotations.query.Area", fields = {
#FieldResult(name="id", column="aid"),
#FieldResult(name="name", column="name")
})
})
I think you can try retrieving results in Object[] e.g. below:
EntityManager entityManager = EntityManager.getEntityManager();
Query query= entityManager.createQuery("select cust, ord from Customer cust left outer join cust.orders ord where cust.customerId = :customerId");
tradeQuery.setParameter("customerId", aCustomerId);
List<Object[]> resultList = (List<Object[]>)query.getResultList();
The retrieved resultsList will be list of Object array containing Customer and Order objects in flat.
if(!resultList.isEmpty()){
Iterator<Object[]> iter = resultList.iterator();
while(iter.hasNext()){
Object[] resultObj = (Object[])iter.next();
Customer customer= (Customer )resultObj[0];
Order order = (Order)resultObj[1];
}
}
Hope this helps!

Hibernate: how do I retrieve my entities from a ScrollableResults?

I do a query that returns a list of entities. How can I retrieve the entities from a ScrollableResults:
Session s = ....;
Query q = s.createQuery("....") # returns 100000s rows
ScrollableResults sr = q.scroll();
sr.scroll(45999); # just a number
Employee employee = ???
How do I get an employee in the last line of code
try the get(0) method, or get()[0]
Here's a link to API: ScrollableResults
get() returns the entire current row, get(index) returns object at index position without initializing the rest of them. There are also a bunch of convenience getXXX() methods that cast result to given type.
I do a query that returns a list of entities. How can I retrieve the entities from a ScrollableResults... How do I get an employee.
Just to improve the other answers, the ScrollableResults does the entity conversion for you although this isn't immediately clear from the Javadocs.
As #Bozho says, calling sr.get() will return the entity at the current location, but wrapped in an array. In looking at the code for ScrollableResultsImpl the current row's result is set with:
if ( result != null && result.getClass().isArray() ) {
currentRow = (Object[]) result;
} else {
currentRow = new Object[] { result };
}
So ScrollableResults.get() always returns an array of results and if your entity is not an array, it will be at get()[0].
So, with your code you would do something like:
while (sr.next()) {
// get the entity which is the first element in an Object[]
Employee employee = sr.get()[0];
...
}
To retrieve entities the simplest way would be to cast the object to whichever object you want:
E.g:
ScrollableResults sr = q.scroll();
while (sr.next()) {
CustomObject object = (CustomObject) sr.get()[0]; // Now CustomObject will have all the properties mapped
}
This works perfect for all the scenarios.

Categories

Resources