Hibernate Envers get revisions for criteria - java

Using Hibernate Envers (4.1.9.Final). Trying to get all the revisions (date, revision number) for which a entities have changed of a certain type and that match a certain criterion.
This is the code that I'm currently having:
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(InventoryItem.class, false, true)
.add(AuditEntity.property("section_uuid").eq(sectionUuid))
.addOrder(AuditEntity.revisionNumber().desc());
List<Object[]> revisions = query.getResultList();
This returns one element for each changed InventoryItem. So, if two InventoryItems were changed in a revision, I get two elements -- I do not want that.
This returns also the actual InventoryItems, I think that's a bit heavy -- I do not want that.
How can I get a distinct collection of revisions (date, revision number)?

On basis of Adam's answer, here is the code that I implemented. I'll mark his answer as the accepted answer.
AuditReader auditReader = AuditReaderFactory.get(entityManager);
AuditQuery query = auditReader.createQuery()
.forRevisionsOfEntity(InventoryItem.class, false, true)
.addProjection(AuditEntity.revisionNumber().distinct())
.addProjection(AuditEntity.revisionProperty("created"))
.add(AuditEntity.property("section_uuid").eq(sectionUuid))
.addOrder(AuditEntity.revisionNumber().desc());
As a result, query.getResultList() will return a collection of Object[], where each Object[] contains:
Object[0]: revision number as int
Object[1]: revision date as java.util.Date, corresponding the created revisionProperty

I think what you are looking for is the addProjection method on AuditQuery. You can add a projection on the revision number and date.

Related

Hibernate Search java spring, searching only entities that have ids as specified

I have a problem when i want to search in entities that have specific ids. I have fullTextQuery that i execute, it works all fine, bud when i want to say
ONLY SEARCH IN THESE ENTITIES (List of ids provided) :
+(title:slovakia~2 leadText:slovakia~2 body:slovakia~2 software:slovakia~2) +verified:true +eid:(113 | 112 | 3)
Then i get 0 results, these entities are indexed and persisted, all should be working fine, yet it doesnt return any results.
Here is The entity property defined :
#Id
#GeneratedValue
#Field(name = "eid")
#FieldBridge(impl = LongBridge.class)
private long id;
I have tried, Without field bridge, with TermVector.YES and also without any additional #Field.. annotation. All results either exception or just no results.
What is a proper way of searching in specific IDs?
For instance here is the working query:
Creation of query looks like this :
return Optional.of(getQueryBuilder()
.keyword()
.onField("eid")
.matching(stringBuilder.toString())
.createQuery());
The syntax you tried to use, (113 | 112 | 3), is not correct in this context. Parameters to the keyword query are not interpreted, in particular operators are not supported.
Use a boolean junction that matches any of the provided IDs instead:
List<String> eids = ...;
QueryBuilder qb = getQueryBuilder();
BooleanJunction<?> idJunction = qb.bool();
for (String eid : eids) {
idJunction.should(
qb.keyword()
.onField("eid")
.matching(eid)
.createQuery()
);
}
return idJunction.createQuery();
Note that, if you want to add other queries, you should not use the same junction. Use another junction that includes idJunction.createQuery() as one of its clauses.
From the little experience i have had with hibernate-search, only Ranges seem to work well with intenger and long fields. In your example here, i expect the following query should work just fine:
QueryBuilder qb = getQueryBuilder();
BooleanJunction<?> idJunction = qb.bool();
bool.must(NumericRangeQuery.newLongRange("eid", Long.valueOf(eid), Long.valueOf(eid), true, true).createQuery();
In this case, the Boxed Long.valueOf() is optional if the values being supplied are Long values already.

Java Mongo: How to get the max of each docuemnt

I have a collection with complex document, each with user Id. each userId has timestamp, so I'd like to return document for all users in organization, with the latest timestamp per each user.
This is what I tried, it sort of worked, except only the timestamp & userId fields were mapped in the result - all other data wasn't transferred:
Criteria criteria = Criteria.where("organization").is("someOrg");
Aggregation agg = newAggregation(
match(criteria),
group("userId").last("timestamp").as("timestamp")
);
AggregationResults<UserPerformanceAlert> groupResults = mongoTemplate.aggregate(agg, collectionName, UserPerformanceAlert.class);
I tried project but it kept giving me exceptions saying "java.lang.IllegalArgumentException: ExposedFields must not be null!"
Note: the full document has complex inner objects that I need to retrieve. normal find() method works just find to serialize the data to my class model.
Thanks!
Well, I just found the issue, I had to specify all the fields at the group() part:
group("userId", "type", "header", "body", "scopes", "accountId").last("timestamp").as("timestamp")

CriteriaBuilder greatest date only

I'm trying to understand how to select ONLY the latest date on a record from a join. My People entity is joined with a Membership entity. My Membership entity has a RefMembershipStatus entity.. I an trying to select ONLY the most current date from the Membership entity... My joins look as follows:
Join<People, Membership> membershipPath = root.join(People_.membershipList);
//Membership has property: Membership_.membershipStatusDate -- I must retrieve ONLY the latest (most current) date in membershipStatusDate..
Join<Membership, RefMembershipStatus> progPath = membershipPath.join(Membership_.refMembershipStatus);
predicateList.add(cb.and(progPath.in(selectedStatus)));
There are two ways how you can do this.
Either you need to add a predicate which finds the max date. In the CriteriaBuilder API you would use the greatest method. It would look something like this:
Root<Membership> membership = criteria.from(Membership.class);
predicateList.add(cb.greatest(membership.get(Membership_.membershipStatusDate)));
Or you could use orderBy and then just select the first result with getFirstResult:
criteriaQuery.orderBy(cb.desc(membership.get(Membership_.membershipStatusDate)));
entityManager.createQuery(criteriaQuery).getFirstResult();

How to search date field for a String using JPA Criteria API

This is a question that spins off my other Question here . I thought it would be best put as a different question after someone(#Franck) pointed me to this link and this one too.
I'm stumped on how to search for a string in a database Date column (in my case MySQL DATETIME) using the JPA Criteria API.
Here's what I've done;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Client> cq = cb.createQuery(Client.class);
Root<Client> entity = cq.from(Client.class);
cq.select(entity);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.like(cb.lower(entity.get("dateJoined").as(String.class)), "%"+search.toLowerCase()+"%"));
cq.where(predicates.toArray(new Predicate[]{}));
TypedQuery<Client> query = em.createQuery(cq); //<--- Error gets thrown here
return query.getResultList();
But it fails with the following exception;
java.lang.IllegalArgumentException: Parameter value [%10-2015%] did not
match expected type [java.lang.Character]
where 10-2015 is the String being searched for;
I'm stuck on how to go by achieving this. I need some help.
Ok, after lots of experimenting with various strategies, here's what I did that finally worked.
I saw this post here and suddenly remembered the JPA Tuple Interface which is an Object that can return multiple result Type(s). So to perform my like comparison, and since Date cannot be simply cast to a String here are the steps;
I get the column as a Tuple
do a check on The Tuple Object to see if it's assignable from Date
if it is, then get the Date-Format expression and pass it to the like expression.
So essentially, here's what I initially had which was apparently failing;
predicates.add(cb.like(cb.lower(entity.get("dateJoined").as(String.class)), "%"+search.toLowerCase()+"%"));
Now, this is what I have that works beautifully;
Path<Tuple> tuple = entity.<Tuple>get("dateJoined");
if(tuple.getJavaType().isAssignableFrom(Date.class)){
Expression<String> dateStringExpr = cb.function("DATE_FORMAT", String.class, entity.get("dateJoined"), cb.literal("'%d/%m/%Y %r'"));
predicates.add(cb.like(cb.lower(dateStringExpr), "%"+search.toLowerCase()+"%"));
}
NOTE-WORTHY CONSIDERATIONS -
I am aware that from wherever the search would be initiated, all my Dates are presented in this form 07/10/2015 10:25:09 PM hence my ability to know how to format the Date for the comparison in my like expression as "'%d/%m/%Y %r'".
This is just one step that works for Dates. Most other Types e.g int, long, char ...etc... can all be directly Cast to String and as I explore more Types of data, I'll definitely do the same for any other Type that cannot be directly Cast to String.
Though this works perfectly for me, but before I mark this as the right answer, I'm going to subject it to some more extensive tests and in the process keep it open for comments by anyone that has any reservations about my strategy.
And finally, to that one person that this helped out in any way... Cheers!
This works in my case H2 (I use it for unit-tests), and I hope will work as well in Postgresql and Oracle, since TO_CHAR function seems to be cross-DB supported.
Path<Date> path = ua.get(MyEntity_.timestamp);
Expression<String> dateStringExpr = cb.function("TO_CHAR", String.class, path, cb.literal("DD.MM.YYYY HH24:MI:SS"));
predicates.add(cb.like(dateStringExpr, "%" + value + "%"));
PS. MyEntity_ stands for metamodel generated for real MyEntity. You may read about Metamodels in Oracle docuemntation for Criteria API.
I would suggest you convert you search string to Date object, and do the comparison
SimpleDateFormat dateFormat = new SimpleDateFormat(...desired date format here...);
Date dateSearchParam = dateFormat.format(search);
predicates.add(cb.eq(entity.get("dateJoined"), dateSearchParam);
Or if you want, you can change the type of your dateJoined attribute in your Entity to String, while your MySQL DB type remains DATETIME. You can utilize JPA #Convert to convert DATETIME to java.lang.String when Entity is retrieved from DB (and vice-versa when Entity is being persisted to DB).
See a sample here.
Attribute Converters are only available in JPA 2.1 version.

How to compare using HIbernate criteria on a string column treating it as a long column

I am working on a web project written in java that uses Hibernate for data access from a oracle database.
I have a column name serial in my database which is defined as VARCHAR(12) even though it contains only values that can be cast into long. My intention is get all serials (treating them as numbers) between numbers fromNo and toNo. I cannot use Restrictions.between because the serial column is not a number in DB. It is defined as String in the associated object as well. Right now I achieve the requirement (in an ugly way) by converting the number range to a list of strings and do a Restricitons.in on the column.
long fromNO = 10;
long toNo = 100;
List<String> listNos = null;
for (long k = fromNo; k <=toNO; k++) {
listNos.add(k.toString());
}
Criteria criteria = getMyHibernateSession().createCriteria(MyObject.class);
criteria.add(Restrictions.in('serialNo',listNos));
List<MyObject> results = criteria.list();
Though I get the desired results, the problem happens when the toNo is not defined by the user and the tool need to get all the serial from fromNo
My question is how can write a hibernate criteria where I can overcome all this problem, by specifying Criteria to treat serial column as long and not String? (Some sort of casting process may be?)
You can add new 'long' field in your entity annotated as
#Formula("cast(serialNo as NUMBER(10,0))")
private int lSerialNo;
Then use the new field lSerialNo in your Restrictions
Please try to use Restrictions.between. Try to parse the integer to string
criteria.add(Restrictions.between('serialNo',String.valueOf(listNosFrom),String.valueOf(listNosTo)));

Categories