How to Query-By-Example with IN clause in spring-jpa? - java

I have a spring-jpa application and fetching rows from database with query-by-example like:
public List<Person> query() {
Person p = new Person();
person.name = "john";
return dao.findAll(Example.of(p));
}
Problem: I want to add an IN clause, like:
WHERE person.name IN ('john', 'jane');
Essentially, an IN clause is the same as an OR matching on a specific field.
So I could achieve the same with person.name = 'john' OR person.name = 'jane'.
BUT: how can I add those conditions into an example object that obviously has no list property?

As #Hitham S. AlQadheeb and #pirho hinted at, this is not supported by Query By Example. The documentation explicitly states:
Currently, only SingularAttribute properties can be used for property matching.
Use an alternative like Derived Queries, Specifications or a custom method implementation.

Related

Spring JPA Criteria Builder specific columns

I'm using JPA criteria builder to query Database for the records. However, I want to return specific columns from a table using JPA criteria API.
Currently, my implementation returns full object. But, I want to return only specific columns and those columns could be dynamic. Below code will query on Person table and returns Full-Object of Person.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
query.select(cb.equal(root.get("id"), 1));
TypedQuery<Person> result = em.createQuery(query);
List<Person> personList = result.getResultList();
I know we can create a minified class like PersonMini which will include only a specific field like below but, In my case, the columns could be dynamic and based on the user's preference.
class PersonMini {
private long id;
private String name;
// .. constructor and getter/setter
}
It's not feasible to create a separate mini-classes of Person. Has anyone solved such issue before or could you please suggest another approach if that would solve my problem.
EDIT:
As mentioned in here.
If the type of the criteria query is CriteriaQuery for some user-defined class X (i.e., a criteria query object created by passing a X class argument to the createQuery method), the elements of the list passed to the multiselect method will be passed to the X constructor and an instance of type X will be returned for each row.
If I use multiselect method and use PersonMini Class then I will need a constructor for each and every combination of the fields. Because the selection of the fields is done by Users. selection of fields is not static.
Suppose, in PersonMini class I have 3 fields. Id, Name, and City then User could choose Id and Name, Name and City, City(alone), Id(alone), Id and City. For each combination I need constructor otherwise it will throw error runtime.
For two-three field, it is feasible to have a different combination of constructor but for 15-20 field it is not.

Hibernate: Fetching columns with their aliases

Consider this trivial query:
SELECT 1 as first, 2 as second
When using Hibernate we can then do something like:
em.createNativeQuery(query).fetchResultList()
However, there seem to be no way of getting the aliases (or column names). This would be very helpful for creating List<Map<String, Object>> where each map would be a row with their aliases, for instance in this case: [{first: 1, second: 2}].
Is there a way to do something like that?
I would suggest a bit different approach which may meet your needs.
In JPA 2.1 there is a feature called "result set mapping".
Basically you have to define a POJO class which would hold the result values (all the values must be passed using the constructor):
public class ResultClass{
private String fieldOne;
private String fieldTwo;
public ResultClass(String fieldOne, String fieldTwo){
this.fieldOne = fieldOne;
this.fieldTwo = fieldTwo;
}
}
Then you have to declare the mapping on one of your entities (does not matter on which, it just has to be a declated #Entity):
#SqlResultSetMapping(name="ResultMapping", classes = {
#ConstructorResult(targetClass = ResultClass.class,
columns = {#ColumnResult(name="columnOne"), #ColumnResult(name="columnTwo")})
})
The columnOne and columnTwo are aliases as declared in the select clause of the native query.
And finally use in the query creation:
List<ResultClass> results = em.createNativeQuery(query, "ResultMapping").getResultList();
In my opinion this is more elegant and "a level above" solution as you are not working with a generic Map key/values pairs but with a concrete POJO class.
You can use ResultTransformer interface . Implement custom mapper for mapping values with aliases.
here is example https://vladmihalcea.com/why-you-should-use-the-hibernate-resulttransformer-to-customize-result-set-mappings/
with ResultTransformer you can easy customize result set type , especially if you need aliases

Possible to query by key instead of value in Hazelcast (using Predicates)?

In Hazelcast, is it possible to query an IMap based on attributes of a key instead of the values? All the Hazelcast examples show querying by value. E.g., for a map of employees with keys that are strings:
IMap<String, Employee> employees;
The typical search predicates then search based on employee attributes (name, salary, etc). But my case uses more complex keys, such as:
IMap<DataAttributes, DataValue> myData;
So if DataAttributes has fields such as:
class DataAttributes {
String theDescription;
Date theStartTime;
public String getDescription() { return theDescription; }
// etc....
}
I want to write a predicate that can query by the keys, to return an appropriate DataValue object. This does not work:
Predicate pred = Predicates.equal("description", "myDescription");
myData.keySet(pred); // Throws IllegalArgumentException: "There is no suitable accessor for..."
I could roll-my-own as suggested in this answer, but I'd rather use an out-of-the-box solution if I can.
It doesn't matter if I wind up using the Criteria API, or the Distributed SQL Query API. Any working query would be great. Bonus points for a solution that works on nested attributes (i.e.: DataAttributes theStartTime.getYear()).
It is possible using PredicateBuilder (com.hazelcast.query.PredicateBuilder). The PredicateBuilder paradigm allows you to query based on keys, like so:
EntryObject eo = new PredicateBuilder().getEntryObject();
Predicate fredWithStartTimeThisYear = eo.key().get("Description").equal("Fred")
.and(eo.key().get("theStartTime.Year").equal(2015));
Note that you can refer to class members by accessor method ("getter") or field name, as you can see in the above example code. I found this information in the "Mastering Hazelcast" online book, available at hazelcast.org (but you have to fill out a registration form to gain access to it).

query that should return entities qith specific related entities

Generally my questian is very simple I think, nevertheless I couldn't find a good solution. Let's say I have an Entity class called MyEntity which has a OneToMany relation to an Entity class called EntityAttribute, so it has a list or set attributes with objects of this class. EntityAttribute has an attribute name of type String.
Now I want to implement a method which takes attribute names and returns all entities that contains for each name in attributes at least one attribute with that name. Although this sounds very straight forward, the only solution I found was to execute a query for each attribute name and merge the results like this:
for (String name : attributeNames) {
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
cq.where(attributeJoin.get(EntityAttribute_.name).equals(name));
cq.select(entity);
... // get result list and merge
}
This code isn't tested but generally is one solution. This doesn't seem to be the most efficient one.
Another solution I testet was to use multiple joins like
CriteriaQuery<MyEntity> cq = cb.createQuery(MyEntity.class);
Root<MyEntity> entity = cq.from(MyEntity.class);
List<Predicate> predicates = new ArrayList<>();
for (String name : attributeNames) {
Join<MyEntity, EntityAttribute> attributeJoin = entity.join(MyEntity_.attributes);
predicates.add(attributeJoin.get(EntityAttribute_.name).equals(name));
}
cq.select(predicates.toArray(new Predicate[] {}));
... // get result list
This seems to be more efficient, but it iterates over the cartesian products... So it's highly inefficient.
I could also imagine to nest subqueries, but this seems to be very complicated.
The question simply is: What is the best solution for this problem? Afterwards I would also like to implement AND and OR, so I can query for all entities with attributes x and (y or z) or something like that. But for now I only want to make the AND case.
Thanks in advance
Maybe you could achieve this using in clause + group by + having + count, if I understand your question correctly. The idea is to count the number of matches for each MyEntity. If the count is equal to the number of attributes passed in, it means that each of them was found for that entity (assuming they are unique). In JPQL the query would look like this:
select e from MyEntity e join e.attributes a
where a.name in (:attributeNames)
group by e having count(*) = :attributeCount
where :attributeCount is the value of attributeNames.size().
I'm not very familiar with the criteria API, but you can experiment with something like this:
...
cq.groupBy(entity);
cq.having(cb.equal(cb.count(entity), attributeNames.size()));
// TODO: add IN clause
...

Hibernate HQL Query : How to set a Collection as a named parameter of a Query?

Given the following HQL Query:
FROM
Foo
WHERE
Id = :id AND
Bar IN (:barList)
I set :id using the Query object's setInteger() method.
I would like to set :barList using a List of objects, but looking at the Hibernate documentation and list of methods I cannot see an obvious choice of which to use. Any ideas?
Use Query.setParameterList(), Javadoc here.
There are four variants to pick from.
I'm not sure about HQL, but in JPA you just call the query's setParameter with the parameter and collection.
Query q = entityManager.createQuery("SELECT p FROM Peron p WHERE name IN (:names)");
q.setParameter("names", names);
where names is the collection of names you're searching for
Collection<String> names = new ArrayList<String();
names.add("Joe");
names.add("Jane");
names.add("Bob");
In TorpedoQuery it look like this
Entity from = from(Entity.class);
where(from.getCode()).in("Joe", "Bob");
Query<Entity> select = select(from);

Categories