Hibernate criteria.list() returns duplicates - java

I know this kind of question has been asked thousands of times already, but I think I have a different problem. So, I have a class A, which has a collection of objects of class B and a getter for this collection:
public class A {
private String Id;
private ArrayList<B> bees;
public ArrayList<B> getBees(){
ArrayList<Criterion> criterions = new ArrayList<>();
criterions.add(Restrictions.eq("classAId", this.Id));
return (ArrayList<B>)DatabaseHelper.getObjectByCriteria(criterions, B.class);
}
}
And DatabaseHelper.getObjectByCriteria code:
public static List getObjectByCriteria(ArrayList<Criterion> cr, Class classType){
List result = new ArrayList();
Session session = MyHibernateUtil.getSessionFactory().openSession();
Criteria criteria = session.createCriteria(classType);
if (cr != null){
for (Criterion criterion : cr){
criteria.add(criterion);
}
}
result = criteria.list();
if (session.isOpen()){
session.close();
}
return result;
}
When I run this query (select * from table_b where id = 123) in MySQLWorkbench I get two different rows with different values.
However, the "result" list returned by DatabaseHelper.getObjectByCriteria contains two indentical objects with identical ids and values.
I don't use any kind of hibernate annotations
I have show_sql property set to "true", and I checked the real query being run by hibernate and its ok

Related

Merging list of two objects to create a third object on the basis of common attribute

I have two different csv files having data on two different entities and I have to merge two different csv files to create one on the basis of sql join type equijoin and left join.
so I have created first entity as class name Customer having attributes:
int CustomerId ;
String CustomerName;
int OrderId;
And List of object of this class like:
Customer c1 = new Customer(CustomerId, CustomerName, OrderId);
1 million objects..
List<Customer> cust = new ArrayList<>();
cust.add(c1);
cust.add(c2);
so on to make list of 1 million object.
Similarly, I have created class of second entity Order having attributes:
int orderId;
String orderName;
Date orderdate;
Order o1 = new Order(orderId, orderName, orderdate);
so on 1 million object
List<Oder> order = new ArrayList<>();
Now I need to merge both the object on the basis of orderId and generate third object having result class having all the attributes from both the classes described above.
Please suggest me solution using java stream 8 to map both the streams of list to create inner join and left join type example in the third new result class.
Aside from the getters, your Customer class should have the following method:
public boolean orderMatch(Order order) {
//fixed the attribute name so it would be in camelCase
return orderId == order.getId();
}
Of course, this implies that Order has a getId() getter method to get its id attribute.
Finally, you'll need a CustomerExtended class.
class CustomerExtended {
int customerId ;
String customerName;
Order customerOrder;
public CustomerExtended(Customer customer, Order order) {
customerId = customer.getId();
customerName = customer.getName();
customerOrder = order;
}
}
Now, you can create a Function which would search for the corresponding Order and append it to a Customer:
Function<Customer,CustomerExtended> extendCustomer = (c) ->{
//I used the more descriptive name orderList instead of o1.
Optional<Order> order = orderList.stream()
.filter(c::orderMatch)
.findFirst();
if(order.isPresent()) {
return new CustomerExtended(c,order.get());
}
return null;
};
And then you can apply it to the Customer list through a map.
List<CustomerExtended> newCustomerList = customerList.stream()
.map(c -> extendCustomer.apply(c))
.collect(Collectors.toList());
EDIT: A few last notes
There should be some check that the ID numbers are not duplicate either when adding the objects to the lists, or when the lists are populated.
For semantic purposes, The Customer object as it is should be renamed CustomerOrder or be separated into an object only for customer info and an object which would store the relation between customer and order.
The case where an order is not found should be better handled and throw an exception.

Getting list of lists in Hibernate

In my project I have two entities: Race and RaceDriver, which has-a Race in it:
class RaceDriver {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "race", nullable = false)
private Race race;
...
#Column(name = "starting_nr")
private Integer startingNr;
...
#Column(name = "disqualified", nullable = false)
private boolean disqualified;
}
Now, what I wanted is to get the list of the startingNrs of the disqualified RaceDrivers in a Race, which looked like this:
public List<Integer> findDisqualifiedDriversStartingNumbers(Integer raceId) {
ProjectionList projection = Projections.projectionList()
.add(Projections.property("startingNr").as("startingNr"));
return getSession()
.createCriteria(RaceDriver.class)
.setProjection(projection)
.add(Restrictions.eq("race.id", raceId))
.add(Restrictions.eq("disqualified", true))
.list();
}
The thing is that now I need the same, but for the few Races. How can I achieve this without making a separate DAO calls? Because I've heard that it is better to make as much as possible in a single database call.
My idea is to simply get the list of the drivers which are disqualified in the given races, and then parse it in the Java code, which I think will require few loops, and make some map of disqualified RaceDriver's starting numbers, where the key would be Race.id.
The DAO attempt looks like that:
public List<RaceDriver> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
return getSession()
.createCriteria(RaceDriver.class)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.list();
}
The problem is that I will get that big objects, instead of some map or list of the only data I need (startingNr and race.id).
So the question is - can I do it somehow using only Hibernate?
Personally I would use the solution in plain java because it'll be more clear for any developer supporting your code in the future.
Answering the question "can it be done via Hibernate?": yes, it can be, ResultTransformer is the right way, especially if map-of-lists conversion is required more than once in your program. There is no standard transformer for your needs but you can write your own one:
public class MapOfListsResultTransformer<K, V> extends BasicTransformerAdapter {
public List transformList(List collection) {
final Map<K, List<V>> map = new HashMap<>();
for (Object object : collection) {
final Object[] objects = (Object[]) object;
final K key = (K) objects[0];
final V value = (V) objects[1];
if (!map.containsKey(key)) {
final List<V> list = new ArrayList<V>();
list.add(value);
map.put(key, list);
} else {
map.get(key).add(value);
}
}
return Arrays.asList(map);
}
}
And its usage is the following:
public Map<Integer, List<Integer>> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
ProjectionList projection = Projections.projectionList()
.add(Projections.property("race.id").as("race.id"))
.add(Projections.property("startingNr").as("startingNr"));
return (Map<Integer, List<Integer>>) getSession()
.createCriteria(RaceDriver.class)
.setProjection(projection)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.setResultTransformer(new MapOfListsResultTransformer<Integer, Integer>())
.uniqueResult();
}
You'll want to create a Query with hibernate that has two WHERE conditions.
#PersistenceContext protected EntityManager em;
List<Integer> raceIds = Arrays.asList(1, 2, 3);
Query query = em.createQuery("FROM RaceDriver r WHERE r.raceId IN (:raceIds) AND r.disqualified = true");
query.setParameter("raceIds", raceIds);
List<RaceDriver> drivers = query.getResultList();
This will return a list of disqualified drivers within the raceIds that you've provided.
Update
After getting List<RaceDriver> you can for loop on drivers and make List of HashMaps with raceId as a key, and a List of startingNr (of disqualified drivers) as a value.
HashMap<Integer,List<Integer>> startingNrHashMap=new...;
for(RaceDriver driver:drivers)
{
List<Integer> strNr = new ArrayList<Integer>();
if(startingNrHashMap.containsKey(driver.race.raceid))
{
//if race id is already present in hash map then
strNr = startingNrHashMap.get(driver.race.raceid);
strNr.add(driver.startingNr);
startingNrHashMap.put(driver.race.raceid,strNr);
}
else
{
// if race id is NOT present in hash map
strNr.add(driver.startingNr);
startingNrHashMap.put(driver.race.raceid,strNr);
}
strNr =null;
}
This is how I did it:
DAO method:
public List<RaceDriver> findDisqualifiedDriversInRaces(List<Integer> raceIds) {
if (!raceIds.isEmpty()) { // it will crash if the list will have no elements!
return getSession()
.createCriteria(RaceDriver.class)
.add(Restrictions.in("race.id", raceIds))
.add(Restrictions.eq("disqualified", true))
.list();
}
return Collections.emptyList();
}
Then to extract from it what I wanted, I created an auxiliary method:
// key = race.id, value = list of disqualified raceDrivers' starting numbers
private HashMap<Integer, List<Integer>> extractStartingNumbersToMap(List<RaceDriver> disqualifiedDrivers) {
HashMap<Integer, List<Integer>> disqualifiedDriversMap = new HashMap<>();
for (RaceDriver raceDriver : disqualifiedDrivers) {
Integer raceId = raceDriver.getRace().getId();
if (!disqualifiedDriversMap.containsKey(raceId)) {
disqualifiedDriversMap.put(raceId, new ArrayList<>(Arrays.asList(raceDriver.getStartingNr())));
} else {
disqualifiedDriversMap.get(raceId).add(raceDriver.getStartingNr());
}
}
return disqualifiedDriversMap;
}
I did it before Mark T has answered, as you can see it's very similar. However I'm posting it, as it could be helpful to someone.
I asked the same kind of question, Its about NHibernate and .NET MVC but you can get idea of DAO and ResultTransformer
Question

JPA query.getResultList()?

I use JPA 1.0:
Query query;
query = em.createNamedQuery("getThresholdParameters");
query.setParameter(1, Integer.parseInt(circleId));
List<Object[]> resultList = new ArrayList();
resultList = query.getResultList();
Here I get result as List<Object[]>, thus I have to type convert all the parameters of the row to their respective types which is cumbersome.
In JPA 2.0 there is TypedQuery which return an entity object of type one specifies.
But as I am using JPA 1 I can't use it.
How to get result as Entity object of type I want??
EDIT:
QUERY
#Entity
#Table(name="GMA_THRESHOLD_PARAMETERS")
#NamedQuery(
name = "getThresholdParameters",
query = "select gmaTh.minNumberOc, gmaTh.minDurationOc, gmaTh.maxNumberIc, gmaTh.maxDurationIc, gmaTh.maxNumberCellId,"
+ "gmaTh.distinctBnumberRatio, gmaTh.minPercentDistinctBnumber from GmaThresholdParameter gmaTh "
+ "where gmaTh.id.circleId=?1 AND gmaTh.id.tspId=?2 AND gmaTh.id.flag=?3 "
)
Your query selects many fields. Such a query always returns a list of Object arrays. If you want a list containing instances of your GmaThresholdParameter entity, then the query should be
select gmaTh from GmaThresholdParameter gmaTh
where gmaTh.id.circleId=?1 AND gmaTh.id.tspId=?2 AND gmaTh.id.flag=?3
The code to get the list of entities would then be
List<GmaThresholdParameter> resultList = query.getResultList();
You'll get a type safety warning from the compiler, that you can ignore.
I can't respond to this as a comment so I'll just go ahead and make it an answer.
List<Object[]> resultList = new ArrayList(); // CREATE an empty ArrayList object
resultList = query.getResultList(); // getResultList ALSO returns its own ArrayList object
And since you assign the list that getResultList() returns to the same variable as you used for your own empty ArrayList, your application loses any connection to your own empty ArrayList and Java will collect it as garbage. Essentially you created it for absolutely no purpose.
what JB Nizet posted is enough.
List<GmaThresholdParameter> resultList = query.getResultList();
I have done something similar since I was using JPA 1 at that time:
final Collection<YourType> typedResult = new ArrayList<YourType>
for(final Object result : query.getResultList())
{
typedResult.add((YourType) result);
}
return typedResult;
List<GmaThresholdParamerter> result= query.getResultList();
for( GmaThresholdParamerter res : result)
{
System.out.println("" +res.getMinNumberOc());
System.out.println("" +res.getMinDurationOc());
}

Java : Set result set from JPA Paginated Query to custom class

Here is the scenario.
I am trying to get a list of records in my resource layer. It has the following code;
PagedResponse<Person> pagedResponse= new PagedResponse<Person>();
There is a call to Business implementation as
pagedResponse = facadeLocator.getPersonFacade().findAllRecords(getUserId(), fromrow, maxrows);
Now in the Business implementation, I use named query as;
Query query = getNamedQuery("Person.findAll");
I have the response as
pagedResponse = executePagedQuery(query, firstResults, maxResults);
and inside executePagedQuery(), I have;
List resultList = query.getResultList();
The response returned pagedResponse is of custom type PagedResponse class with 2 members;
private Long totalRecords;
private List<T> records;
Now in my Person class, I have
#NamedQuery(name = "Person.findAll", query = "Select DISTINCT(p.personName), p.personAge, p.personGender from Person p where p.personAge = :personAge")
Now here is what happens at runtime.
I get the "records" as Vector with members as
[0] = Object[]
[1] = Object[]
Coming back to the resource layer, I want to iterate through the response and set it in a list
List<Person> personList = new ArrayList<Person>();
So what are the options I have.
I have tried doing
for(Object[] person: pagedResponse.getRecords()) {
Person p = new Person();
p.setPersonName((String)person[0]);
// Setting other values
personList.add(p);
}
But it says incompatible types for the line
for(Object[] person: pagedResponse.getRecords()) {
Just to add, I did not face any incompatible type issue when my query did not return select columns and instead returned all the columns like;
query = "SELECT p FROM Person p WHERE
So I have 2 questions;
1. Why was there no type casting issues when I was returning all the columns using the named query (It showed the type as "Person" and not generic type as showing after using the named query with specific columns)
2. Using the query with specific columns, what is the right approach to set the values returned from the query in the resource layer ?
The query with many individual SELECTed values is supposed to return a list of lists. Maybe you want to define a bean with an appropriate constructor:
package com.foo;
public class PersonData {
private String name;
private int age;
private Sex gender;
public PersonData(String name, int age, Sex gender) {
this.name = name;
this.age= age;
this.gender = gender;
}
// getters/setters
}
And run the query as:
SELECT NEW com.foo.PersonData(DISTINCT(p.personName), p.personAge, p.personGender)
FROM Person p WHERE p.personAge = :personAge
Now getResultList() should return a list of PersonData objects. Though I haven't used the nested new PersonData(DISTINCT(...)) syntax...

JPA Query.getResultList() - use in a generic way

I'm creating a complex query with multiple tables and need to list the result. Usually, I'm using the EntityManager and map the result to the JPA-Representation:
UserEntity user = em.find(UserEntity.class, "5");
Then I can access all values as the user UserEntity class defines it. But how can I access the field-values returned from a native, multiple-table query? What I get is a List of Objects. That's fine so far, but what "is" that Object? Array? Map? Collection? ...
//simpleExample
Query query = em.createNativeQuery("SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id");
List list = query.getResultList();
//do sth. with the list, for example access "something" for every result row.
I guess the answer is quite simple, but most examples out there just show the usage when directly casting to a targetClass.
PS: In the example I could use the class-mappings of course. But in my case someTable is not managed by JPA, and therefore I don't have the entity nor do I have a class-representation of it, and since I'm joining like 20 tables, I don't want to create all the classes just to access the values.
General rule is the following:
If select contains single expression and it's an entity, then result is that entity
If select contains single expression and it's a primitive, then result is that primitive
If select contains multiple expressions, then result is Object[] containing the corresponding primitives/entities
So, in your case list is a List<Object[]>.
Since JPA 2.0 a TypedQuery can be used:
TypedQuery<SimpleEntity> q =
em.createQuery("select t from SimpleEntity t", SimpleEntity.class);
List<SimpleEntity> listOfSimpleEntities = q.getResultList();
for (SimpleEntity entity : listOfSimpleEntities) {
// do something useful with entity;
}
If you need a more convenient way to access the results, it's possible to transform the result of an arbitrarily complex SQL query to a Java class with minimal hassle:
Query query = em.createNativeQuery("select 42 as age, 'Bob' as name from dual",
MyTest.class);
MyTest myTest = (MyTest) query.getResultList().get(0);
assertEquals("Bob", myTest.name);
The class needs to be declared an #Entity, which means you must ensure it has an unique #Id.
#Entity
class MyTest {
#Id String name;
int age;
}
The above query returns the list of Object[]. So if you want to get the u.name and s.something from the list then you need to iterate and cast that values for the corresponding classes.
I had the same problem and a simple solution that I found was:
List<Object[]> results = query.getResultList();
for (Object[] result: results) {
SomeClass something = (SomeClass)result[1];
something.doSomething;
}
I know this is defenitly not the most elegant solution nor is it best practice but it works, at least for me.
Here is the sample on what worked for me. I think that put method is needed in entity class to map sql columns to java class attributes.
//simpleExample
Query query = em.createNativeQuery(
"SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id",
NameSomething.class);
List list = (List<NameSomething.class>) query.getResultList();
Entity class:
#Entity
public class NameSomething {
#Id
private String name;
private String something;
// getters/setters
/**
* Generic put method to map JPA native Query to this object.
*
* #param column
* #param value
*/
public void put(Object column, Object value) {
if (((String) column).equals("name")) {
setName(String) value);
} else if (((String) column).equals("something")) {
setSomething((String) value);
}
}
}
What if you create a bean with all required properties and cast the result using Java 8+ streams?
Like this:
public class Something {
private String name;
private String something;
// getters and setters
}
And then:
import javax.persistence.Query;
...
Query query = em.createNativeQuery("SELECT u.name,s.something FROM user u, someTable s WHERE s.user_id = u.id", Something.class);
List<?> list = query.getResultList();
return list
.stream()
.map(item -> item instanceof Something ? (Something) item : null)
.collect(Collectors.toList());
That way, you don't need to return List<Object[]> nor hide the warning with #SuppressWarnings("unchecked")
Ps.:
1 - I know that this post is very old. But... I'm here in 2021, so others will be coming here too =)
2 - This is wrong or bad practice? Let me know :D
You can also update your hibernate to a version greater than 5.4.30.final

Categories