everyone.
My task is to get the last added record from my database. Since the last of the records in the database has the maximum id, I wrote a method like this, but it always returns null to me.
#Override
public Address getLast() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Address> criteriaQuery = criteriaBuilder.createQuery(Address.class);
Root<Address> root = criteriaQuery.from(Address.class);
criteriaQuery
.select(root)
.orderBy(criteriaBuilder.desc(root.get(Address_.id)));
TypedQuery<Address> findAllSizes = entityManager.createQuery(criteriaQuery);
return findAllSizes.getResultStream().findFirst().orElse(null);
}
All works fine. All I needed to add was add #Transactional on the method which calls the specified method
Related
I'm setting up a method to filter entries by the "lastupdated" column. I'm trying to filter entries of which the lastupdated value is between a given startTime and endTime.
I'm using the simplified code below:
public List<SomeEntity> getItemsByLastUpdated() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<SomeEntity> criteriaQuery = cb.createQuery(SomeEntity.class);
var root = criteriaQuery.from(SomeEntity.class);
var predicates = new ArrayList<>();
var startTime = Instant.now();
var endTime = Instant.now().plus(5, MINUTES);
predicates.add(cb.greaterThanOrEqualTo(root.get("lastupdated"), startTime));
predicates.add(cb.lessThan(root.get("lastupdated"), endTime));
criteriaQuery.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(criteriaQuery).getResultList();
}
For some reason, the criteria for datetime fields are not (correctly) being applied: All the entities are being returned instead of only the items within the startTime-endTime range.
I'm guessing that I need to explicitly state that the "lastupdated" field is a datetime field; when I set startTime to Instant.MAX, I receive an error that seems to hint on a date filter instead of datetime:
Invalid value for EpochDay (valid values -365243219162 - 365241780471): 365241780838
Would anyone know how I can filter on a datetime field being between two given java Instants?
PS. I'm aware of using derived queries such as findByXGreaterThanOrEqualToAndXLessThan(Instant instant1, Instant instant2); but since there are several other criterias which I have not included, this option is not feasible.
I think you could easily do the same using EntityManager and JPQL instead of Criteria API:
public List<SomeEntity> getItemsByLastUpdated() {
var query = "select e from SomeEntity e
where e.lastupdated >= :from
and e.lastupdated < :to
and your_other_criteria";
return entityManager.createQuery(query)
.setParameter("from", startTime)
.setParameter("to", endTime)
.getResultList();
}
It took me quite some time to figure it out; turned out that the lack of filtering didn't have anything to do with the CriteriaBuilder setup.
I had created a test to verify the method, and prepared some entities before running the test. However, the "lastupdated" field in the entity class was annotated with #UpdateTimestamp, which would result in all the saved entities having pretty much the same 'lastupdated' value.
I am currently using mongoTemplate in Spring boot like this:
public MyEntity update(MyDto dto) {
...
MyEntity result = mongoTemplate.findAndModify(
query, update, MyEntity.class);
return result;
}
query puts in the Criteria that finds the MyEntity to be modified, and update puts the contents to change. However, the returned value is the data before the update. How can I get the modified value right away?
When using findAndModify on the mongoTemplate, you have to explicitly configure it if the updated record shall be returned instead of the original one.
This may be done the following way:
FindAndModifyOptions findAndModifyOptions = FindAndModifyOptions.options().returnNew(true);
MyEntity result = mongoTemplate.findAndModify(query, update, findAndModifyOptions, MyEntity.class);
return result;
https://docs.mongodb.com/manual/reference/method/db.collection.findAndModify/
I implemented pageable functionality into Criteria API query and I noticed increased memory usage during query execution. I also used spring-data-jpa method query to return same result, but there memory is cleaned up after every batch is processed. I tried detaching, flushing, clearing objects from EntityManager, but memory use would keep going up, occasionally it will drop but not as much as with method queries. My question is what could cause this memory use if objects are detached and how to deal with it?
Memory usage with Criteria API pageable:
Memory usage with method query:
Code
Since I'm also updating entities retrieved from DB, I use approach where I save ID of last processed entity, so when entity gets updated query doesen't skip next selected page. Below I provide code example that is not from real app I'm working on, but it just recreation of the issue I'm having.
Repository code:
#Override
public Slice<Player> getPlayers(int lastId, Pageable pageable) {
List<Predicate> predicates = new ArrayList<>();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Player> criteriaQuery = criteriaBuilder.createQuery(Player.class);
Root<Player> root = criteriaQuery.from(Player.class);
predicates.add(criteriaBuilder.greaterThan(root.get("id"), lastId));
criteriaQuery.where(criteriaBuilder.and(predicates.toArray(Predicate[]::new)));
criteriaQuery.orderBy(criteriaBuilder.asc(root.get("id")));
var query = entityManager.createQuery(criteriaQuery);
if (pageable.isPaged()) {
int pageSize = pageable.getPageSize();
int offset = pageable.getPageNumber() > 0 ? pageable.getPageNumber() * pageSize : 0;
// Fetch additional element and skip it based on the pageSize to know hasNext value.
query.setMaxResults(pageSize + 1);
query.setFirstResult(offset);
var resultList = query.getResultList();
boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
} else {
return new SliceImpl<>(query.getResultList(), pageable, false);
}
}
Iterating through pageables:
#Override
public Slice<Player> getAllPlayersPageable() {
int lastId = 0;
boolean hasNext = false;
Pageable pageable = PageRequest.of(0, 200);
do {
var players = playerCriteriaRepository.getPlayers(lastId, pageable);
if(!players.isEmpty()){
lastId = players.getContent().get(players.getContent().size() - 1).getId();
for(var player : players){
System.out.println(player.getFirstName());
entityManager.detach(player);
}
}
hasNext = players.hasNext();
} while (hasNext);
return null;
}
I think you are running into a query plan cache issue here that is related to the use of the JPA Criteria API and how numeric values are handled. Hibernate will render all numeric values as literals into an intermediary HQL query string which is then compiled. As you can imagine, every "scroll" to the next page will be a new query string so you gradually fill up the query plan cache.
One possible solution is to use a library like Blaze-Persistence which has a custom JPA Criteria API implementation and a Spring Data integration that will avoid these issues and at the same time improve the performance of your queries due to a better pagination implementation.
All your code would stay the same, you just have to include the integration and configure it as documented in the setup section.
I want to set a parameter in a named query (JPA 2.0), so my dataTable would render the respective dataSet. The parameter is obtained remotely and injected in a AbstractFacade class.
I've tried to achieve this through the code above, but it's not working.
Can someone help me?
AbstractFacade (main code):
private String prefDep;
public List<T> findByPrefDep() {
prefDep= FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("xPrefDep");
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).setParameter("prefDep", prefDep).getResultList();
}
The Entity class (main code):
#NamedQuery(name = "Capacitacao.findByPrefDep", query = "SELECT c FROM Capacitacao c WHERE c.prefDep = :prefDep"),
The AbstractController:
public Collection<T> getItems() {
if (items == null) {
items = this.ejbFacade.findByPrefDep();
}
return items;
}
There is no exception launched, but the dataSet rendered corresponds to a findAll named query.
Thanks in advance.
Your code doesn't use your named query at all. A named query has a name, and your code doesn't use that name anywhere.
Use
getEntityManager().createNamedQuery("Capacitacao.findByPrefDep", Capacitacao.class)
.setParameter("prefDep", prefDep)
.getResultList();
You could have found that yourself by simply reading the EntityManager javadoc.
How do I update Object with Spring Data and MongoDB?
do I just do a template.save()?
public Person update( String id, String Name )
{
logger.debug("Retrieving an existing person");
// Find an entry where pid matches the id
Query query = new Query(where("pid").is(id));
// Execute the query and find one matching entry
Person person = mongoTemplate.findOne("mycollection", query, Person.class);
person.setName(name);
/**
* How do I update the database
*/
return person;
}
If you read the javadoc for MongoOperations/MongoTemplate you will see that
save()
performs an:
upsert()
So yes you can just update your object and call save.
You could probably do both 'find' and 'update' operations in one line.
mongoTemplate.updateFirst(query,Update.update("Name", name),Person.class)
You can find some excellent tutorials at
Spring Data MongoDB Helloworld
You can just use template.save() or repository.save(entity) methods for this. But mongo has also Update object for this operations.
For example:
Update update=new Update();
update.set("fieldName",value);
mongoTemplate.update**(query,update,entityClass);
Below code is the equivalent implementation using MongoTemplate for update operation.
public Person update(Person person){
Query query = new Query();
query.addCriteria(Criteria.where("id").is(person.getId()));
Update update = new Update();
update.set("name", person.getName());
update.set("description", person.getDescription());
return mongoTemplate.findAndModify(query, update, Person.class);
}