Hibernate Criteria using FIND_IN_SET - java

I have a below requirement in my project:
(1) Request object to an API which is off list type (of strings).
(2) We are using Hibernate Criteria to fetch the resultSet from the mysql database.
(3) One of the columns is having Pipe delimited values.
(4) We have to fetch the rows against each of the request object's values.
Table: user_info
-----------------------------------------------------------
user_id user_name .. .. .. .. hobbies
-----------------------------------------------------------
101 johnSmith .. .. .. .. Traveling|Painting|Gardening
102 tomWatch .. .. .. .. Gardening|Reading|Fishing
103 robertPatt .. .. .. .. Dancing|Gardening|Swimming|Blogging
104 julieAdams .. .. .. .. Bicycling|Fishing|Socializing
JSON Request object:
{
searchParams : {
hobbies : [
"Gardening",
"Fishing",
"Reading"
],
..
..
..
}
}
Java Code
if(!CollectionUtils.isEmpty(searchParams.getHobbies())) {
List<String> reqHobbies = searchParameters.getHobbies();
Disjunction disjunction = Restrictions.disjunction();
for(String hobby : reqHobbies) {
Criterion criterion = Restrictions.like("hobbies", "|" + hobby + "|");
disjunction.add(criterion);
}
criteria.add(disjunction);
}
This query will not work as the starting of the value (from table) is not having "|" symbol..
If I modify the critieria API to generate the query in the following fashion,
select * from user_info
where hobbies like '%Gardening|%' or hobbies like '%Fishing|%' or hobbies like '%Reading|%'
or hobbies like '%|Gardening|%' or hobbies like '%|Fishing|%' or hobbies like '%|Reading|%'
or hobbies like '%|Gardening%' or hobbies like '%|Fishing%' or hobbies like '%|Reading%';
This design also has a flaw. The only way to solve this by using FIND_IN_SET.
Mysql query:
select * from user_info
where hobbies find_in_set('Gardening', replace(hobbies, '|', ','))
or hobbies find_in_set('Fishing', replace(hobbies, '|', ','))
or hobbies find_in_set('Reading', replace(hobbies, '|', ','));
How to create a find_in_set query using Hibernate Criteria? Using #Formula ?

If your goal is building query dynamically and not using Criteria API, you may build it with FluentJPA, like in this:
public List<UserInfo> filterByHobbies(List<String> hobbies) {
Function1<UserInfo, Boolean> dynamicFilter = buildOr(hobbies);
FluentQuery query = FluentJPA.SQL((UserInfo user) -> {
SELECT(user);
FROM(user);
WHERE(dynamicFilter.apply(hobbies);
});
return query.createQuery(getEntityManager(), UserInfo.class).getResultList();
}
private Function1<UserInfo, Boolean> buildOr(List<String> hobbies) {
Function1<UserInfo, Boolean> criteria = Function1.FALSE();
for (String hobby : hobbies)
criteria = criteria.or(u -> FIND_IN_SET(parameter(hobby),
u.getHobbies().replace('|', ',')) > 0);
return criteria;
}

This is an option in some cases:
xxxRepository.findAll((root, query, cb) -> {
List<Predicate> predicateList = new ArrayList<>();
if (StringUtils.isNotBlank(param)) {
Expression<Integer> findInSetFun = cb.function("FIND_IN_SET", Integer.class,
cb.literal(param), root.get("targetColName"));
predicateList .add(cb.greaterThan(findInSetFun, 0));
}
}, pageable);

Expression<Integer> function = criteriaBuilder.function("find_in_set", Integer.class,
criteriaBuilder.literal(warehouseList), root.get("sWarehouseIdList"));
Predicate sWarehouseIdList = criteriaBuilder.greaterThan(function, 0);
predicateList.add(sWarehouseIdList);

Related

Select entities that have a field with occurence different from using spring jpa

I have an entity named certificate
#Entity
public class CertificateData {
#Id private String id;
private long expireDate;
private long createDate;
private int status;
private String subjectDN;
...
}
Many certificate can have the same subject and I want to select all certificates that have count of the field subjectId different from 3. I used this code and it worked
public List<CertificateData> findAllByRedundancy() {
Map<String, Integer> subjectDNCount = new HashMap<>();
Map<String, List<CertificateData>> subjectDNCertificates = new HashMap<>();
List<CertificateDto> certificateDataList =
this.certificaterepository
.findAll().forEach(certificateData -> {
String subject = certificateData.getSubjectDN();
if(subjectDNCount.containsKey(subject)){
subjectDNCount.put(subject, subjectDNCount.get(subject)+1);
subjectDNCertificates.get(subject).add(certificateData);
}
else{
subjectDNCount.put(subject, 1);
subjectDNCertificates.put(subject, new ArrayList<>());
subjectDNCertificates.get(subject).add(certificateData);
}
});
List<CertificateDto> result = new ArrayList<>();
subjectDNCount.forEach((s, i) -> {
if(i!=3){
result.addAll(subjectDNCertificates.get(s));
}
});
return result;
}
I tried to do the same thing using a Query-annotated Spring JPA which looks like this:
#Query("select c from CertificateData c group by c.subjectDN Having count(c) <> 3")
List<CertificateData> findAllByRedundancy();
But it doesn't return all the certificates. It returns only certificates by distincts subjectDN.
It looks like the query in your annotation is selecting groups instead of the CertificateData records. One way to get the CertificateData records themselves would be to use a sub-query to find the desired subjectDN values (which is what you already have), and then have the outer query find the records with those subjectDN values. I haven't tested this yet, but in JPQL it would be something like:
#Query(
"select c from CertificateData c where c.subjectDN in " +
"(" +
" select c.subjectDN from CertificateData c " +
" group by c.subjectDN having count(c) <> 3" +
")"
)
List<CertificateData> findAllByRedundancy();
For reference, the SQL (specifically postgres) to do this would be something like:
select * from certificate_data where subject_dn in
(
select subject_dn from certificate_data
group by subject_dn having count(*) <> 3
)

Hibernate: Criteria & createSQLQuery: how to get proper JSON?

When I try this I get the proper JSON as a result, but it takes a lot of time:
Criteria c = sessionFactory.getCurrentSession().createCriteria(User.class);
List<User> users = c.list();
List<User> specialUsers = new ArrayList<>();
for (User user : users) {
List<Perm> userPerms = user.getProfile().getPerms();
for (Perm perm : userPerms) {
if (perm.getId().equals(SPECIAL_ID)) {
specialUsers.add(user);
}
}
}
return specialUsers;
and the JSON is like:
[{"id":111,"name":"Name111"},{"id":222,"name":"Name222"}]
In attempt to improve performance I tried code below. In SQL app the results are OK, a few records of users:
String sql = "SELECT u.id, u.name FROM app.user u inner join app.perms p where u.profile = p.profile AND p.right= :rightId";
List<User> specialUsers= (List<User>)sessionFactory.getCurrentSession()
.createSQLQuery(sql)
.setParameter("rightId", SPECIAL_ID)
.list();
return specialUsers;
Now the 'JSON' however looks like this:
[[111,"Name111"],[222,"Name222"]]
I tried several things, like select *, criteria.add(Restrictions...) but to no effect. What I noticed is that in the first case specialUsers.toString returns proper data, in the second case it returns meaningless Strings like Ljava.lang.Object;#23e1469f.
Any hints how to solve this?
I managed to solve this in this way, may not be perfect:
// get ids of all special users
String sql = "SELECT u.id FROM app.user u inner join app.perms p where u.profile = p.profile AND p.right= :rightId";
List<Integer> intIds = sessionFactory.getCurrentSession()
.createSQLQuery(sql)
.setParameter("rightId", SPECIAL_ID)
.list();
// convert to long values
List<Long> longIds = intIds.stream()
.mapToLong(Integer::longValue)
.boxed().collect(Collectors.toList());
// get all special users
Criteria c = sessionFactory
.getCurrentSession()
.createCriteria(User.class)
.add(Restrictions.in("id", longIds));
List<User> specialUsers = c.list();
return specialUsers;
}

Retrieve a row from DB as a Map in Hibernate

Table Players:
ID | name | email | age | ...
1 | 'bob' | null | 23 | ...
This table is where instances of class Player are persisted (one row per instance, no composition etc.).
Having a Hibernate Session, how do I get the row (say with id - the PK - equal to 1) as a Java Map (key = column name, value = cell value) ?
Example usage:
Map<String,String> row = getPlayerByIdAsMap(1);
Use a query with AliasToEntityMapResultTransformer; is verbose but should works with Hibernate property definition and not with JavaBean definition (they can differ).
Map<String,Object> aliasToValueMap =
session.createCriteria(User.class)
.add(Restrictions.idEq(userID))
.setProjection(Projections.projectionList()
.add(Projections.id().as("id"))
// Add others properties
)
.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE)
.uniqueResult();
A worse approch can be write a custom ResultTransformer that introspect ClassMetadata and try to extract values...
class IntrospectClassMetadata extends BasicTransformerAdapter {
PassThroughResultTransformer rt = PassThroughResultTransformer.INSTANCE;
public Object transformTuple(Object[] tuple, String[] aliases) {
final Object o = rt.transformTuple(tuple, aliases);
ClassMetadata cm = sf.getClassMetadata(o.getClass());
List<String> pns = new ArrayList<String>(Arrays.asList(cm.getPropertyNames()));
Map<String, Object> m = new HashMap<String, Object>();
for(String pn : pns) {
m.put(pn, cm.getPropertyValue(o, pn));
}
m.put(cm.getIdentifierPropertyName(), cm.getIdentifier(o));
return m;
}
}
and use
Map<String,Object> aliasToValueMap =
session.createCriteria(User.class)
.add(Restrictions.idEq(userID))
.setResultTransformer(new IntrospectClassMetadata())
.uniqueResult();
Last chance:
Map<String,Object> map = (Map<String,Object>)s.createSQLQuery("select * from user where id = :id")
.setParameter("id",p.id)
.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE)
.uniqueResult();
but this doesn't map list,bags and other mapped object, but only raw column names and values...
You can use HQL and do a query for selecting the result as a new Map
select new Map(p.id as ID, p.name as name, p.email as email, p.age as age)
from Player p
It will return you a collection of maps, being each one of the maps a row in the query result.
You can use BeanUtils and do something like this:
User user = (User) session.get(User.class, userID);
Map map = BeanUtils.describe(user);

createSqlQuery Hibernate, Java

I need to write hibernate query (createSQLquery) in Java land
I have query but I bit lost how to create criteria for my query.
basically I have to transfer regular where clause to hibernate .add(Restriction)
for instance lets take:
where (name LIKE '%test%' or lastname LIKE '%test%') and (site_id = 2) and (date>=121212 and date <= 343343)
Query query = sess.createSQLQuery(sql).addScalar("ID", Hibernate.long);
query.add(Restrictions.or (
Restrictions.like("name", "%test%")
Restrictions.like("lastname", "Fritz%")
))
.add(Restrictions.eq("site_id", new Integer(2)))
.add(Restrictions.add(Restrictions.gr("date_start", 122122)
.add(Restrictions.le("date_end", 345433)));
I did not have chance to run it since do not have access to my environment now, just would love to figure out this.
Thanks
You have to do like this
Criteria criteria = session.createCriteria(YourPojo.class);
criteria.add(Restrictions.eq("id", id));
criteria .add(Restrictions.or (
Restrictions.like("name", "%test%"),
Restrictions.like("lastname", "Fritz%")));
criteria.add(Restrictions.eq("site_id", new Integer(2)))
criteria.add(Restrictions.add(Restrictions.gr("date_start", 122122)
.add(Restrictions.le("date_end", 345433)));
return (criteria.list());
okay, problem solved this way:
Query from XML - I put in where clause
<Applications>
<Application name="AlertData">
<Query name="alerts">
<SQL>
SELECT
mr.measurement_date as measurementDate,
........ // more data
FROM measurement m
LEFT JOIN tags ON mr.tag_id = tags.tag_id
.......... // more joins
WHERE
mr.site_id = :siteid
$searchConstraints$ // just a string
$dateStartFragment$ // just a string
</SQL>
</Query>
</Application>
</Applications>
Next Java code:
if (search != null && !search.isEmpty()) {
searchFragment = " AND (UPPER(field1) LIKE :search" +
" OR UPPER(field2) LIKE :search)";
}
sql = this.prepareQuery(sql, ImmutableMap.of(
"searchConstraints", searchFragment,
"dateStartFragment", dateStartFragment
));
Query query = this.sessionFactory.getCurrentSession()
.createSQLQuery(sql)
//more query here
if (search != null && !search.isEmpty()) {
query.setString("search", "%" + search.toUpperCase() + "%");
}
public String prepareQuery(String queryString, Map<String, String> templateVars) {
ST stringTemplater = new ST(queryString, '$', '$');
for (String key : templateVars.keySet()) {
stringTemplater.add(key, templateVars.get(key));
}
String renderedQuery = stringTemplater.render();
return renderedQuery;
}

Why does Hibernate HQL work, but Criteria does not?

I have a simple model which is just a chain of one-to-many relationships: Country -< City -< Street
The tables are mapped as entities and are returned as Map.
The following test method is producing odd results:
public static void main(String[] args) {
Session session = HibernateSessionFactory.getSession();
List<Map<String, Object>> results = null;
//Query using HQL and print results
System.out.println("FROM HQL =====================");
String hql = "from Street where City.Country.countryid = 1";
Query query = session.createQuery(hql);
results = query.list();
for(Map<String, Object> row : results) {
System.out.println(row);
}
//Query using Criteria and print results
System.out.println("FROM CRITERIA ================");
Criteria criteria = session.createCriteria("Street");
criteria.add(Restrictions.eq("City.Country.countryid", 1));
results = criteria.list();
for(Map<String, Object> row : results) {
System.out.println(row);
}
}
The top block which uses the HQL works as expected, but the bottom block falls over:
Output:
FROM HQL =====================
{streetname=Mayfair, City=org.hibernate.proxy.map.MapProxy#2b12e7f7, $type$=Street, streetid=1}
{streetname=Park Lane, City=org.hibernate.proxy.map.MapProxy#2b12e7f7, $type$=Street, streetid=2}
{streetname=Bond Street, City=org.hibernate.proxy.map.MapProxy#663b1f38, $type$=Street, streetid=3}
{streetname=Old Kent Road, City=org.hibernate.proxy.map.MapProxy#663b1f38, $type$=Street, streetid=4}
FROM CRITERIA ================
Exception in thread "main" org.hibernate.QueryException: could not resolve property: City.Country.countryid of: Street
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:67)
at org.hibernate.persister.entity.AbstractPropertyMapping.toColumns(AbstractPropertyMapping.java:82)
at org.hibernate.persister.entity.BasicEntityPropertyMapping.toColumns(BasicEntityPropertyMapping.java:54)
at org.hibernate.persister.entity.AbstractEntityPersister.toColumns(AbstractEntityPersister.java:1367)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumns(CriteriaQueryTranslator.java:457)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getColumnsUsingProjection(CriteriaQueryTranslator.java:417)
at org.hibernate.criterion.SimpleExpression.toSqlString(SimpleExpression.java:68)
at org.hibernate.loader.criteria.CriteriaQueryTranslator.getWhereCondition(CriteriaQueryTranslator.java:357)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:113)
at org.hibernate.loader.criteria.CriteriaJoinWalker.<init>(CriteriaJoinWalker.java:82)
at org.hibernate.loader.criteria.CriteriaLoader.<init>(CriteriaLoader.java:91)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1578)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:306)
at com.bar.foo(Main.java:33)
I can't see why the HQL can resolve City.Country.countryid but Criteria (Restrictions) can't.
Am I missing something obvious?
Because you used the wrong syntax for the hql query.
several faults:
Street.class instead of "Street"
Missing alias
use country directly instead of country.id
Try this:
Criteria criteria = session.createCriteria(Street.class)
.createAlias("city", "ci")
.add(Restrictions.eq("ci.country", country))
See the hibernate reference for more details.

Categories

Resources