Get random object from SQL database via Hibernate - java

I have following MySql dependent code ( ORDER BY RAND() ) . I would like to know if there is hibernate HQL alternative for it (admin is boolean tag indicating that the user as an admin). This is working code:
public long getRandomAdmin() {
Session session = getSession();
Query selectQuery = session.createSQLQuery("SELECT user_id FROM users WHERE admin = '1' ORDER BY RAND()");
selectQuery.setMaxResults(1);
List<BigInteger> list = null;
try {
list = selectQuery.list();
} catch (HibernateException e) {
log.error(e);
throw SessionFactoryUtils.convertHibernateAccessException(e);
}
if (list.size() != 1) {
log.debug("getRandomAdmin didn't find any user");
return 0;
}
log.debug("found: " + list.get(0));
return list.get(0).longValue();
}

See this link:
http://www.shredzone.de/cilla/page/53/how-to-fetch-a-random-entry-with-hibernate.html
Criterion restriction = yourRestrictions;
Object result = null; // will later contain a random entity
Criteria crit = session.createCriteria(Picture.class);
crit.add(restriction);
crit.setProjection(Projections.rowCount());
int count = ((Number) crit.uniqueResult()).intValue();
if (0 != count) {
int index = new Random().nextInt(count);
crit = session.createCriteria(Picture.class);
crit.add(restriction);
result = crit.setFirstResult(index).setMaxResults(1).uniqueResult();
}
This is what you want. Keep Hibernate as an abstraction layer while still being able to query a random object. Performance suffers a bit, though.
Although I've been using Hibernate a lot, I don't know a more elegant way that is easy to use. Imho you should wrap that method behind a facade.

Since the Criterion used in the accepted answer is deprecated, I figured out how to do it with the CriteriaBuilder & CriteriaQuery and just wanted to share it here. I used the pattern described here to extend my repository by the custom method:
#Repository
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
#Autowired
EntityManager em;
public User findRandomUserInCountry(String countryCode) throws NotFoundException {
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Long> cqCount = qb.createQuery(Long.class);
Root<User> userCountRoot = cqCount.from(User.class);
cqCount.select(qb.count(userCountRoot)).where(qb.equal(userCountRoot.get("countryCode"), countryCode));
int count = em.createQuery(cqCount).getSingleResult().intValue();
System.out.println("Count of users: " + count);
if (0 != count) {
int index = new Random().nextInt(count);
CriteriaQuery<User> cqUser = qb.createQuery(User.class);
Root<User> userRoot = cqUser.from(User.class);
cqUser.select(userRoot).where(qb.equal(userRoot.get("countryCode"), countryCode));
User randomUser = em.createQuery(cqUser).setFirstResult(index).setMaxResults(1)
.getSingleResult();
System.out.println("Random user: " + randomUser.getName());
return randomUser;
} else {
throw new NotFoundException("No users available in repository for country: " + countryCode);
}
}
}

Related

How do I get a specific EntityManager?

I have an entity with ~70 variables. And I want to make some custom queries for searching for filtering, using different combinations of them, without having to create 10 000 find methods and then applying some logic to pick the correct one to use.
#PersistenceContext
EntityManager entityManager;
private CriteriaBuilder criteriaBuilder;
public PageImpl<TClaimModel> findAllWithFilters(TClaimModelSearchCriteria filters){
if (criteriaBuilder == null) criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TClaimModel> criteriaQuery = criteriaBuilder.createQuery(TClaimModel.class);
Root<TClaimModel> claimRoot = criteriaQuery.from(TClaimModel.class);
Predicate predicate = getPredicate(filters, claimRoot);
criteriaQuery.where(predicate);
if(filters.getSortDirection().equals(Sort.Direction.ASC))
criteriaQuery.orderBy(criteriaBuilder.asc(claimRoot.get(filters.getSort())));
else
criteriaQuery.orderBy(criteriaBuilder.desc(claimRoot.get(filters.getSort())));
TypedQuery<TClaimModel> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setFirstResult(filters.getPage() * filters.getPageSize());
typedQuery.setMaxResults(filters.getPageSize());
long claimsCount = getClaimsCount(predicate);
return new PageImpl<>(typedQuery.getResultList(), filters.getPageable(), claimsCount);
}
private Predicate getPredicate(TClaimModelSearchCriteria filters, Root<TClaimModel> claimRoot) {
List<Predicate> predicates = new ArrayList<>();
if (Objects.nonNull(filters.getClaimNum()))
predicates.add(criteriaBuilder.like(claimRoot.get("claimNum"), "%" + filters.getClaimNum() + "%"));
if (Objects.nonNull(filters.getClaimDate()))
predicates.add(criteriaBuilder.equal(claimRoot.get("claimDate"), filters.getClaimDate()));
if (Objects.nonNull(filters.getStartSearchDate())) {
if (Objects.nonNull(filters.getEndSearchDate()))
predicates.add(criteriaBuilder.between(claimRoot.get("claimDate"), filters.getStartSearchDate(), filters.getEndSearchDate()));
else
predicates.add(criteriaBuilder.greaterThanOrEqualTo(claimRoot.get("claimDate"), filters.getStartSearchDate()));
} else if (Objects.nonNull(filters.getEndSearchDate()))
predicates.add(criteriaBuilder.lessThanOrEqualTo(claimRoot.get("claimDate"), filters.getEndSearchDate()));
// repeat for each possible filter;
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private long getClaimsCount(Predicate predicate) {
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
Root<TClaimModel> countRoot = countQuery.from(TClaimModel.class);
countQuery.select(criteriaBuilder.count(countRoot)).where(predicate);
return entityManager.createQuery(countQuery).getSingleResult();
}
This is my filtering code so far, but I get an error "Not an entity: class com.activiti.entities.alfresco.TClaimModel". After reading about this error, it turns out that I can't use #PersistenceContext to get the EntityManager.
So then, HOW do I get the EntityManager? I can autowire an EntityManagerFactory, but it doesn't have the createQuery method.
EDIT: The project is using 2 databases. Using #Autowire on the EntityManager also doesn't work, as I get:
Field entityManager in com.activiti.repository.alfresco.TClaimCriteriaRepository required a single bean, but 2 were found:
- org.springframework.orm.jpa.SharedEntityManagerCreator#0: defined by method 'createSharedEntityManager' in null
- org.springframework.orm.jpa.SharedEntityManagerCreator#1: defined by method 'createSharedEntityManager' in null
As a workaround, I manually built the request...
#Autowired
protected DataSourceService dataSourceService;
public Map<String, Object> findAllWithFilters(TClaimModelSearchCriteria filters){
Map<String, Object> response = new HashMap<>();
try {
List<DataSource> dataSources = dataSourceService.getDataSourcesForTenant(SecurityUtils.getCurrentUserObject().getTenantId());
CommonJdbcTemplate commonJdbcTemplate = new CommonJdbcTemplate();
if (dataSources.size() > 0) {
JdbcTemplate jdbcTemplate = commonJdbcTemplate.getJdbcTemplate(dataSources.get(0));
String sql = buildRequestSql(filters);
List<Map<String, Object>> data = jdbcTemplate.queryForList(sql);
String counter = buildCountRequestSql(filters);
Long count = (Long) jdbcTemplate.queryForList(counter).get(0).get("count");
response.put("total", count);
response.put("data", data);
return response;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String buildRequestSql(TClaimModelSearchCriteria filters) {
StringBuilder sql = new StringBuilder();
sql.append("select * from t_claim");
getWhere(sql, filters);
sql.append(" order by " + filters.getSort());
if (filters.getSortDirection() == Sort.Direction.DESC)
sql.append(" desc");
if (filters.getPage() != 0) {
sql.append(" offset " + filters.getPage() * filters.getPageSize());
}
sql.append(" fetch first " + filters.getPageSize() + " row only");
return sql.toString();
}
private String buildCountRequestSql(TClaimModelSearchCriteria filters) {
StringBuilder sql = new StringBuilder();
sql.append("select count(*) from t_claim");
getWhere(sql, filters);
return sql.toString();
}
...
But I'd still like to know the proper way to do it :P

How to get selected fields from table using hibernate (I am not using Criteria object but CriteriaQuery object so setProjection solution won't work)

I want to get selected fields from table with filters on the top of it.
For example :
If I want to filter my results on the basis of an attribute in the table named "created_on", I would do something like http://example.com/users/?created_on = 01-12-2016
I am achieving above task by doing this :
public List<User> searchUser(List<SearchCriteria> params
, int offset, int limit) {
Session session = mysqlConnectionFactory.getSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root r = query.from(User.class);
Predicate predicate = builder.conjunction();
if (params != null) {
for (SearchCriteria param : params) {
if (param.getOperation().equalsIgnoreCase(">")) {
predicate = builder.and(predicate,
builder.greaterThanOrEqualTo(r.get(param.getKey()),
param.getValue().toString()));
} else if (param.getOperation().equalsIgnoreCase("<")) {
predicate = builder.and(predicate,
builder.lessThanOrEqualTo(r.get(param.getKey()),
param.getValue().toString()));
} else if (param.getOperation().equalsIgnoreCase(":")) {
if (r.get(param.getKey()).getJavaType() == String.class) {
predicate = builder.and(predicate,
builder.like(r.get(param.getKey()),
"%" + param.getValue() + "%"));
} else {
predicate = builder.and(predicate,
builder.equal(r.get(param.getKey()), param.getValue()));
}
}
}
query.where(predicate);
}
List<User> Users = session.createQuery(query)
.setFirstResult(offset).setMaxResults(limit).getResultList();
return Users;
}
Now I also wish to incorporate the functionality which will return selected fields for me, how can I do that?

How to convert query object into a List of Strings?

I got a Login class:
if((!(name.equals(null) || name.equals("")) && !(password.equals(null) || password.equals(""))))
{
try{
loggedUser = checkLogin(name, hexPass.toString());
if(!loggedUser.isEmpty())
{
userdbId = loggedUser.get(0);
userdbName = loggedUser.get(1);
userdbPsw = loggedUser.get(2);
userdbType = loggedUser.get(3);
...
And a user DAO:
public List<String> checkLogin(String uname, String password) {
Session session = null;
List<String> lst = new ArrayList<String>();
try {
session = getSession();
String sql = "select * from user where uname='" + uname + "' and password='" + password + "'";
Query query = session.createSQLQuery(sql)
.addScalar("Id", StringType.INSTANCE)
.addScalar("uname", StringType.INSTANCE)
.addScalar("password", StringType.INSTANCE)
.addScalar("utype", StringType.INSTANCE);
lst = query.list();
}
catch (Exception e){
e.printStackTrace();
}
return lst;
}
The userdbId = loggedUser.get(0); generates error: 500
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to java.lang.String
com.se.pl.actions.LoginAction.execute(LoginAction.java:95)
I do not understand why this is happening since list of 4 strings is going to be put into 4 variables.
The error means that you have an Object and you are doing an implicit cast to String, so that's the error.
userdbId = loggedUser.get(0);
You can fix that by adding toString() method like:
userdbId = loggedUser.get(0).toString();
Of course, you are going to get the string representation for that object.
Note that if you have your own object you are going to get the following representation:
getClass().getName() + '#' + Integer.toHexString(hashCode())
In case you have this, you will need to override the toString() method in your class
You should use TypedQuery<T> instead of query and create objects of user entity type(if you have it defined - you definitely should have one) - it should look something like this (do not take this code literaly! I mainly use OpenJPA)
TypedQuery<UserEntity> query= session.createQuery("SELECT u FROM UserEntity u WHERE name=:name AND password=:password");
query.addParam("name",name);
query.addParam("password",password);
This probably should help:
Query query = session.createSQLQuery(sql)
.addScalar("Id", StringType.INSTANCE)
.addScalar("uname", StringType.INSTANCE)
.addScalar("password", StringType.INSTANCE)
.addScalar("utype", StringType.INSTANCE)
.setResultTransformer(org.hibernate.Criteria.ALIAS_TO_ENTITY_MAP);
Map row = (Map) query.uniqueResult();
lst.add((String) row.get("Id"));
lst.add((String) row.get("uname"));
lst.add((String) row.get("password"));
lst.add((String) row.get("utype"));

An easier way to build queries in Play Framework 1.x / JPA using "in"?

Is there a shortcut in Play Framework / JPA to save building this query manually?
public List<User> getAllExceptThese(Collection<String> emails) {
checkArgument(!emails.isEmpty());
StringBuilder query = new StringBuilder("email not in (");
boolean first = true;
for (String email : emails) {
if (!first) {
query.append(", ");
}
first = false;
query.append("?");
}
query.append(")");
return findAll(query.toString(), emails.toArray());
}
I don't know about Play-framework, but if it's possible to create JPQL-queries, how about building a Query-object and using it to insert the collection instead...
public List<User> getAllExceptThese(Collection<String> emails) {
checkArgument(!emails.isEmpty());
String queryStr = "FROM User u WHERE u.email NOT IN (:excludedEmails)";
Query query = entityManager.createQuery(queryStr);
query.setParameter("excludedEmails", emails);
return (List<User>)query.getResultList();
}

Hibernate = Column not found

I am running an aggregate function in java through hibernate and for some reason it is giving me this error:
INFO Binary:182 - could not read column value from result set: l_date; Column 'l_date' not found.
When I run the MySQL query the column names are l_date and logins and I can not figure out why it is not finding that.
I have tested the query in MySQL and verified that it does work. My function looks as follows.
public List<Logins> getUserLoginsByMonth() {
Session session = getSessionFactory().openSession();
ArrayList<Logins> loginList = null;
try {
String SQL_QUERY = "SELECT l_date as l_month, SUM(logins) as logins FROM (SELECT DATE_FORMAT(login_time, '%M') as l_date, COUNT(DISTINCT users) as logins FROM user_logins WHERE login_time > DATE_SUB(NOW(), INTERVAL 1 YEAR) GROUP BY DATE(login_time)) AS Z GROUP BY(l_month)";
Query query = session.createSQLQuery(SQL_QUERY);
List results = query.list();
for(ListIterator iter = results.listIterator(); iter.hasNext() ) {
Objects[] row = (Object[])iter.next();
System.out.println((Date)row[0}]);
System.out.println((BigInteger)row[1]);
}
}
catch(HibernateException e) {
throw new PersistenceDaoException(e);
}
finally {
session.close();
}
}
You need to add aliases as:
query.addScalar("l_month");
query.addScalar("logins");
and then call query.list();
It should work fine. addScalar is part of SqlQuery.

Categories