Get previous version of entity in Hibernate Envers - java

I have an entity loaded by Hibernate (via EntityManager):
User u = em.load(User.class, id)
This class is audited by Hibernate Envers. How can I load the previous version of a User entity?

Here's another version that finds the previous revision relative to a "current" revision number, so it can be used even if the entity you're looking at isn't the latest revision. It also handles the case where there isn't a prior revision. (em is assumed to be a previously-populated EntityManager)
public static User getPreviousVersion(User user, int current_rev) {
AuditReader reader = AuditReaderFactory.get(em);
Number prior_revision = (Number) reader.createQuery()
.forRevisionsOfEntity(User.class, false, true)
.addProjection(AuditEntity.revisionNumber().max())
.add(AuditEntity.id().eq(user.getId()))
.add(AuditEntity.revisionNumber().lt(current_rev))
.getSingleResult();
if (prior_revision != null)
return (User) reader.find(User.class, user.getId(), prior_revision);
else
return null
}
This can be generalized to:
public static T getPreviousVersion(T entity, int current_rev) {
AuditReader reader = AuditReaderFactory.get(JPA.em());
Number prior_revision = (Number) reader.createQuery()
.forRevisionsOfEntity(entity.getClass(), false, true)
.addProjection(AuditEntity.revisionNumber().max())
.add(AuditEntity.id().eq(((Model) entity).id))
.add(AuditEntity.revisionNumber().lt(current_rev))
.getSingleResult();
if (prior_revision != null)
return (T) reader.find(entity.getClass(), ((Model) entity).id, prior_revision);
else
return null
}
The only tricky bit with this generalization is getting the entity's id. Because I'm using the Play! framework, I can exploit the fact that all entities are Models and use ((Model) entity).id to get the id, but you'll have to adjust this to suit your environment.

maybe this then (from AuditReader docs)
AuditReader reader = AuditReaderFactory.get(entityManager);
User user_rev1 = reader.find(User.class, user.getId(), 1);
List<Number> revNumbers = reader.getRevisions(User.class, user_rev1);
User user_previous = reader.find(User.class, user_rev1.getId(),
revNumbers.get(revNumbers.size()-1));
(I'm very new to this, not sure if I have all the syntax right, maybe the size()-1 should be size()-2?)

I think it would be this:
final AuditReader reader = AuditReaderFactory.get( entityManagerOrSession );
// This could probably be declared as Long instead of Object
final Object pk = userCurrent.getId();
final List<Number> userRevisions = reader.getRevisions( User.class, pk );
final int revisionCount = userRevision.size();
final Number previousRevision = userRevisions.get( revisionCount - 2 );
final User userPrevious = reader.find( User.class, pk, previousRevision );

Building off of the excellent approach of #brad-mace, I have made the following changes:
You should pass in your EntityClass and Id instead of hardcoding and assuming the Model.
Don't hardcode your EntityManager.
There is no point setting selectDeleted, because a deleted record can never be returned as the previous revision.
Calling get single result with throw and exception if no results or more than 1 result is found, so either call resultlist or catch the exception (this solution calls getResultList with maxResults = 1)
Get the revision, type, and entity in one transaction (remove the projection, use orderBy and maxResults, and query for the Object[3] )
So here's another solution:
public static <T> T getPreviousRevision(EntityManager entityManager, Class<T> entityClass, Object entityId, int currentRev) {
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Object[]> priorRevisions = (List<Object[]>) reader.createQuery()
.forRevisionsOfEntity(entityClass, false, false)
.add(AuditEntity.id().eq(entityId))
.add(AuditEntity.revisionNumber().lt(currentRev))
.addOrder(AuditEntity.revisionNumber().desc())
.setMaxResults(1)
.getResultList();
if (priorRevisions.size() == 0) {
return null;
}
// The list contains a single Object[] with entity, revinfo, and type
return (T) priorRevision.get(0)[0];
}

From the docs:
AuditReader reader = AuditReaderFactory.get(entityManager);
User user_rev1 = reader.find(User.class, user.getId(), 1);

Related

Can I find by a list of IDs instead the classic findById() in Spring Data JPA?

I am working on a Spring Boot project using Spring Data JPA and I am wondering if exist a nice and elegant solution to the following use case.
Originally I had a service method like this (it works fine):
#Override
public Page<LogDTO> findAll(Integer logType, LocalDateTime startDate, LocalDateTime endDate, Pageable pageable) throws NotFoundException {
if(startDate == null) startDate = LocalDateTime.of(1900,1,1,0,0,0);
if(endDate == null) endDate = LocalDateTime.now();
Page<EventLog> eventLogs = null;
if(logType != null) {
Optional<LogType> logTypeEntity = logTypeRepository.findById(logType);
logTypeEntity.orElseThrow( () -> new NotFoundException(String.format("Log type doesn't exists with id: ", Integer.toString(logType))));
eventLogs = logRepository.findByLogTypeAndTimestampBetween(logTypeEntity.get(),startDate,endDate, pageable);
} else {
eventLogs = logRepository.findByTimestampBetween(startDate,endDate,pageable);
}
return eventLogs.map(m -> conversionService.convert(m, LogDTO.class));
}
As you can see in this old method take this Integer logType input parameter representing the ID of single log type. Then retrieve the related LogType object by:
Optional<LogType> logTypeEntity = logTypeRepository.findById(logType);
and uses this logTypeEntity instance in order to retrieve all the EventLog object related to this specific single retrieve LogType, by this line:
eventLogs = logRepository.findByLogTypeAndTimestampBetween(logTypeEntity.get(),startDate,endDate, pageable);
Now I was refactoring this method in order to try to obtains the logs related to multiple LogType. So I am changing my original service method in this way:
#Override
public Page<LogDTO> findAll(List<Integer> logTypesList,
LocalDateTime startDate,
LocalDateTime endDate,
Pageable pageable) throws NotFoundException {
if(startDate == null) startDate = LocalDateTime.of(1900,1,1,0,0,0);
if(endDate == null) endDate = LocalDateTime.now();
Page<EventLog> eventLogs = null;
if(logTypesList != null & logTypesList.size() > 0) {
Optional<LogType> logTypeEntity = logTypeRepository.findById(logType);
logTypeEntity.orElseThrow( () -> new NotFoundException(String.format("Log type doesn't exists with id: ", Integer.toString(logType))));
eventLogs = logRepository.findByLogTypeAndTimestampBetween(logTypeEntity.get(),startDate,endDate, pageable);
} else {
eventLogs = logRepository.findByTimestampBetween(startDate,endDate,pageable);
}
return eventLogs.map(m -> conversionService.convert(m, LogDTO.class));
}
As you can see my service method now take a List logTypesList parameter instead a single Integer logType). Then in my code I am checking if this list contains element.
Now I am wondering if exist a smart way in Spring Data JPA to replace this line:
Optional<LogType> logTypeEntity = logTypeRepository.findById(logType);
in order to retrieve the collection of the LogType belonging to all the IDs contained into my List logTypesList. I know that I can simply interate on this collection and add the retrieved objects to a result list but I am wondering if I can implement this behavior in a smarter way.
Following a similar doubt related to how to replace in a smart and elegant way this line:
eventLogs = logRepository.findByLogTypeAndTimestampBetween(logTypeEntity.get(),startDate,endDate, pageable);
Exist a way to pass a collection of LogType objects in order to retrieve all the returned LogEvent objects?
This one (findAllById) should work
List<T> findAllById(Iterable<ID> var1);
Please look at session.byMultipleIds multiLoad
https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/Session.html
It will be something like:
List<LogType> logTypes = session.byMultipleIds( LogType.class ).multiLoad( 1L, 2L, 3L );

Java Mongo DB Unable to get valid next id

I am trying insert an item in MongoDB using Java MongoDB driver.Before inserting I am trying to get nextId to insert,but not sure why I am always getting nextId as 4 .I am using below given method to get nextId before inserting any item in Mongo.
private Long getNextIdValue(DBCollection dbCollection) {
Long nextSequenceNumber = 1L;
DBObject query = new BasicDBObject();
query.put("id", -1);
DBCursor cursor = dbCollection.find().sort(query).limit(1);
while (cursor.hasNext()) {
DBObject itemDBObj = cursor.next();
nextSequenceNumber = new Long(itemDBObj.get("id").toString()) + 1;
}
return nextSequenceNumber;
}
I have total 13 record in my mongodb collection.What I am doing wrong here?
Please don't do that. You don't need create a bad management id situation as the driver already do this in the best way, just use the right type and annotation for the field:
#Id
#ObjectId
private String id;
Then write a generic method to insert all entites:
public T create(T entity) throws MongoException, IOException {
WriteResult<? extends Object, String> result = jacksonDB.insert(entity);
return (T) result.getSavedObject();
}
This will create a time-based indexed hash for id's which is pretty much more powerful than get the "next id".
https://www.tutorialspoint.com/mongodb/mongodb_objectid.htm
How can you perform Arithmetic operations like +1 to String
nextSequenceNumber = new Long(itemDBObj.get("id").toString()) + 1;
Try to create a Sequence collection like this.
{"id":"MySequence","sequence":1}
Then use Update to increment the id
// Query for sequence collection
Query query = new Query(new Criteria().where("id").is("MySequence"));
//Increment the sequence by 1
Update update = new Update();
update.inc("sequence", 1);
FindAndModifyOptions findAndModifyOptions = new FindAndModifyOptions();
findAndModifyOptions.returnNew(true);
SequenceCollection sequenceCollection = mongoOperations.findAndModify(query, update,findAndModifyOptions, SequenceCollection.class);
return sequenceModel.getSequence();
I found the work around using b.collection.count().I simply find the total count and incremented by 1 to assign id to my object.

Morphia - How to replace LongIdEntity.StoredId in last version?

I just switched to the last version of Morphia (1.0.1). The previous one was com.github.jmkgreen.morphia 1.2.3.
I don't know how to replace LongIdEntity.StoredId. I use it to increment a long id.
edit : Here is how it worked before:
public Key<Snapshot> save(PTSnapshot entity) {
if (entity.getId() == null) {
String collName = ds.getCollection(getClass()).getName();
Query<StoredId> q = ds.find(StoredId.class, "_id", collName);
UpdateOperations<StoredId> uOps = ds.createUpdateOperations(StoredId.class).inc("value");
StoredId newId = ds.findAndModify(q, uOps);
if (newId == null) {
newId = new StoredId(collName);
ds.save(newId);
}
entity.setId(newId.getValue());
}
return super.save(entity);
}
StoredId class is just a POJO with 3 fields:
id
className (to store the type of object the auto-increment will be done on, but you could store something lese, this is just used to retrieve the adequate increment value, because you could have more than one auto-incremented collection !)
value (to store the current value of the auto-increment)
But it is just an helper, you can reproduce the behavior all by yourself.
Basically you just need a collection where you store a simple number, and increment it with findAndModify() each time a new object is inserted.
My thought is that Morphia/Mongo decided to remove this because auto-increments are not recommended with Mongo databases, and ObjectIds are more powerful.
Thanks.
Here is the answer:
if (entity.getId() == null) {
DBCollection ids = getDatastore().getDB().getCollection("ids");
BasicDBObject findQuery = new BasicDBObject("_id", getClass().getSimpleName());
DBObject incQuery = new BasicDBObject("$inc", new BasicDBObject("value", 1));
DBObject result = ids.findAndModify(findQuery, incQuery);
entity.setId(result == null || !result.containsField("value") ? 1L : (Long) result.get("value"));
}

Hibernate org.hibernate.criterion.Example.create OR clause

I'm using org.hibernate.criterion.Example.create to create my query from my Entity object. Everything is fine, but using this method the SQL is only created with AND clause between the restrictions.
Is it possible to use org.hibernate.criterion.Example.create but with OR clause?
The short answer is no, you can not do it, but you can implement a OrExample, it's pretty easy, only check the source code of the Example and change the and for or (see sourcecode line 329). Since the methods are protected, you can extend it and override just the necesary.
Something like this:
public class OrExample extends org.hibernate.criterion.Example {
#Override
protected void appendPropertyCondition(
String propertyName,
Object propertyValue,
Criteria criteria,
CriteriaQuery cq,
StringBuffer buf)
throws HibernateException {
Criterion crit;
if ( propertyValue!=null ) {
boolean isString = propertyValue instanceof String;
if ( isLikeEnabled && isString ) {
crit = new LikeExpression(
propertyName,
( String ) propertyValue,
matchMode,
escapeCharacter,
isIgnoreCaseEnabled
);
}
else {
crit = new SimpleExpression( propertyName, propertyValue, "=", isIgnoreCaseEnabled && isString );
}
}
else {
crit = new NullExpression(propertyName);
}
String critCondition = crit.toSqlString(criteria, cq);
if ( buf.length()>1 && critCondition.trim().length()>0 ) buf.append(" or ");
buf.append(critCondition);
}
See the or instead of the original and.
Yes, you can
session.createCriteria(Person.class) .add(Restrictions.disjunction() .add(Restrictions.eq("name", "James")) .add(Restrictions.eq("age", 20)) );
In the example above, class Person would have properties name and age and you would be selecting those people with name = "James" or age = 20.
an old post from SO may be helpful: Hibernate Criteria Restrictions AND / OR combination
Criteria criteria = getSession().createCriteria(clazz);
Criterion rest1= Restrictions.and(Restrictions.eq("A", "X"),
Restrictions.in("B", Arrays.asList("X","Y")));
Criterion rest2= Restrictions.and(Restrictions.eq("A", "Y"),
Restrictions.eq("B", "Z"));
criteria.add(Restrictions.or(rest1, rest2));

Get entity group count always return 0

Following the GAE official doc i try to test it in my local dev environment(unit test), unfortunately the entity group count always return 0:
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
MemcacheService memcacheService = MemcacheServiceFactory.getMemcacheService();
Entity entity1 = new Entity("Simple");
Key key1 = ds.put(entity1);
Key entityGroupKey = Entities.createEntityGroupKey(key1);
//should print 1, but 0
showEntityGroupCount(ds, memcacheService, entityGroupKey);
Entity entity2 = new Entity("Simple", key1);
Key key2 = ds.put(entity2);
//should print 2, but still 0
showEntityGroupCount(ds, memcacheService, entityGroupKey);
below are copied from the doc for quick reference:
// A simple class for tracking consistent entity group counts
class EntityGroupCount implements Serializable {
long version; // Version of the entity group whose count we are tracking
int count;
EntityGroupCount(long version, int count) {
this.version = version;
this.count = count;
}
}
// Display count of entities in an entity group, with consistent caching
void showEntityGroupCount(DatastoreService ds, MemcacheService cache, PrintWriter writer,
Key entityGroupKey) {
EntityGroupCount egCount = (EntityGroupCount) cache.get(entityGroupKey);
if (egCount != null && egCount.version == getEntityGroupVersion(ds, null, entityGroupKey)) {
// Cached value matched current entity group version, use that
writer.println(egCount.count + " entities (cached)");
} else {
// Need to actually count entities. Using a transaction to get a consistent count
// and entity group version.
Transaction tx = ds.beginTransaction();
PreparedQuery pq = ds.prepare(tx, new Query(entityGroupKey));
int count = pq.countEntities(FetchOptions.Builder.withLimit(5000));
cache.put(entityGroupKey,
new EntityGroupCount(getEntityGroupVersion(ds, tx, entityGroupKey), count));
tx.rollback();
writer.println(count + " entities");
}
}
Any ideas about this problem? Thanks in advance.
Entities.createEntityGroupKey() is being called twice as a result of method nesting. Change both occurrences of
showEntityGroupCount(ds, memcacheService, entityGroupKey);
to
showEntityGroupCount(ds, memcacheService, key1);
and the correct counts appear (in the development environment anyway).

Categories

Resources