How can I initialize lazy associations using Spring Data JPA when the query is specified in #Query annotation? - spring

Here's my query before I converted over to Spring Data JPA. Notice how I used to use Hibernate.initialize() to manually fetch the widget's messages.
public Object findWidget(final Widget findMe) {
Widget widget = getJpaTemplate().execute(new JpaCallback<Widget>() {
public Widget doInJpa(EntityManager em) throws PersistenceException {
Query q = em.createQuery("SELECT h FROM " + entityClass.getName() + " h where h.widgetId = ? ");
q.setParameter(1, findMe.getId());
Widget found = (Widget)q.getSingleResult();
//Initialize lazy associations
if(found!= null){
Hibernate.initialize(widget.getMessages());
}
return found;
}
});
return widget;
}
And here's what my query function looks like now. Notice there is no body to put the Hibernate.initialize() in.
#Query("SELECT h FROM Widget h where h.widgetId = ?1 ")
public AccessPoint findWidget(String widgetId);
So how can I specify that the widget's messages are to be fetched actively and not lazily?

Try a fetch join, something like this:
#Query("SELECT h FROM Widget h LEFT JOIN FETCH h.messages WHERE h.widgetId = ?1 ")
public AccessPoint findWidget(String widgetId);
http://docs.oracle.com/html/E24396_01/ejb3_langref.html#ejb3_langref_fetch_joins

Soon after I posted this question, I realized that I am trying to put functionality into the DAO layer that really belongs in the service layer.
So now I initialize the lazy associations using Hibernate.initialize(widget.getMessages()) in my WidgetService class, after I call WidgetDAO.findWidget().
I really should have been doing it this way all along.
EDIT: #MikeN has a good point. A fetch join is the real answer since it is implementation-independent and gets all necessary information in the original query.

Related

How to implement a list of DB update queries in one call with SpringBoot Webflux + R2dbc application

The goal of my springBoot webflux r2dbc application is Controller accepts a Request including a list of DB UPDATE or INSERT details, and Response a result summary back.
I can write a ReactiveCrudRepository based repository to implement each DB operation. But I don't know how to write the Service to group the executions of the list of DB operations and compose a result summary response.
I am new to java reactive programing. Thanks for any suggestions and help.
Chen
I get the hint from here: https://www.vinsguru.com/spring-webflux-aggregation/ . Ideas are :
From request to create 3 Monos
Mono<List> monoEndDateSet -- DB Row ids of update operation;
Mono<List> monoCreateList -- DB Row ids of new inserted;
Mono monoRespFilled -- partly fill some known fields;
use Mono.zip aggregate the 3 monos, map and aggregate the Tuple3 to Mono to return.
Below are key part of codes:
public Mono<ChangeSupplyResponse> ChangeSupplies(ChangeSupplyRequest csr){
ChangeSupplyResponse resp = ChangeSupplyResponse.builder().build();
resp.setEventType(csr.getEventType());
resp.setSupplyOperationId(csr.getSupplyOperationId());
resp.setTeamMemberId(csr.getTeamMemberId());
resp.setRequestTimeStamp(csr.getTimestamp());
resp.setProcessStart(OffsetDateTime.now());
resp.setUserId(csr.getUserId());
Mono<List<Long>> monoEndDateSet = getEndDateIdList(csr);
Mono<List<Long>> monoCreateList = getNewSupplyEntityList(csr);
Mono<ChangeSupplyResponse> monoRespFilled = Mono.just(resp);
return Mono.zip(monoRespFilled, monoEndDateSet, monoCreateList).map(this::combine).as(operator::transactional);
}
private ChangeSupplyResponse combine(Tuple3<ChangeSupplyResponse, List<Long>, List<Long>> tuple){
ChangeSupplyResponse resp = tuple.getT1().toBuilder().build();
List<Long> endDateIds = tuple.getT2();
resp.setEndDatedDemandStreamSupplyIds(endDateIds);
List<Long> newIds = tuple.getT3();
resp.setNewCreatedDemandStreamSupplyIds(newIds);
resp.setSuccess(true);
Duration span = Duration.between(resp.getProcessStart(), OffsetDateTime.now());
resp.setProcessDurationMillis(span.toMillis());
return resp;
}
private Mono<List<Long>> getNewSupplyEntityList(ChangeSupplyRequest csr) {
Flux<DemandStreamSupplyEntity> fluxNewCreated = Flux.empty();
for (SrmOperation so : csr.getOperations()) {
if (so.getType() == SrmOperationType.createSupply) {
DemandStreamSupplyEntity e = buildEntity(so, csr);
fluxNewCreated = fluxNewCreated.mergeWith(this.demandStreamSupplyRepository.save(e));
}
}
return fluxNewCreated.map(e -> e.getDemandStreamSupplyId()).collectList();
}
...

Sorting a custom JPA query with pageable

So, I've already done this using the standard Spring Data JPA interface which extends PagingAndSortingRepository in order to achieve pagination and sorting for a REST API. The thing is, now I want to achieve the very same thing but now using just vanilla JPA and so far so good I managed to get my API to paginate but the sorting doesn't work at all. Every time I try to set the parameter (from a pageable object using pageable.getSort()) it ends with a query error (either if I just send a string as parameter like "name" or just send the sort object, it shows errors).
Here's some code:
My repo implementation:
#Override
public List<Project> findByAll(Pageable pageable) {
Query query = em.createQuery("SELECT project FROM Project project ORDER BY :sort");
query.setParameter("sort", pageable.getSort());
query.setMaxResults(pageable.getPageSize());
query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
return query.getResultList();
}
My service:
#Override
public Page<Project> findAll(Pageable pageable) {
objects = Lists.newArrayList(repository.findByAll(pageable));
PageImpl<Project> pages= new PageImpl<Project>(objects, pageable, repository.count());
return pages;
}
To be clear, I'm filling the Pageable object via URI and from the console I can say it's actually getting the data, so I assume the problem is with the repo.
Edit: This is the error I get when I replace the setParameter("sort", ...) for a hardcoded string aka query.setParameter("sort", "name"):
java.lang.NumberFormatException: For input string: "name"
And I think this method should stand for strings as well. If I use query.setParameter("sort", pageable.getSort()), the error is the same.
The order by cannot be set as a query parameter. Also, the Pageable.getSort().toString() likely won't return a string suitable for use in an order by clause as it will result in a String that represents the Order as property: ORDER, note the colon.
Here are some modifications that will work, assuming Java 8...
String order = StringUtils.collectionToCommaDelimitedString(
StreamSupport.stream(sort.spliterator(), false)
.map(o -> o.getProperty() + " " + o.getDirection())
.collect(Collectors.toList()));
Query query = em.createQuery(
String.format("SELECT project FROM Project project ORDER BY %s", order));

Fetch history of records using LINQ

I am using entity framework with repository pattern and unit of work objects..
I have an entity Request with properties "RequestId", "OldRequestId", which can be accessed using requestRepository object.
eg: requestRepostiory.GetAll(), requestRepository.GetFiltered(r=> r.Requestid =10)
If I pass a RequestId, it should retrieve me the specific record.
If the OldRequestId is not null in the retrieved record, it should bring the old request data as well.
It should go on until the OldRequestId is null.
Simple way would be something like this:
public static IEnumerable<Data> GetRecursive(int id)
{
while (true)
{
var tmp = GetFiltered(x => x.Requestid == id);
yield return tmp;
if (tmp.OldRequestId.HasValue)
id = tmp.OldRequestId.Value;
else
yield break;
}
}
Please note, that this code would run make multiple queries towards the database. Performance won't be the best, but it might work for your scenario.

How to set a Http Session parameter in a JPA named query

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.

Entity Framework LINQ insert command failing. MVC3

Hi I'm trying to do a basic update based on an id using Linq and the entity framework. I'm very new to this but I do not see the problem.
My entity class object is declared at the controller level.
gwwbnEntities db = new gwwbnEntities();
The Method grabs a querystring id and updates the user's registration status who is represented by that id.
public ActionResult ConfirmedAccount(int id)
{
var q = from u in db.user_registration
where u.id == id && u.reg_status == null
select u;
if (q.Any())
{
foreach(var item in q){
user_registration user = item;
user.reg_status = 202;
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
}
return View();
}
else
{
return RedirectToAction("RegistrationError");
}
}
Any help would be greatly appreciated! Again everything works and populates correctly, but the context object.SaveChanges() method fails everytime.
Thanks guys!
The exception you are seeing is because you have an open data reader (foreach) and you are trying to create transaction (EF does it for you) in SaveChanges(). Call SaveChanges outside the loop.
In addtion: Don't set the state to Modified - EF will detect that properties changed and will automatically set the state accordingly. You may want to do .ToList() on the q before doing anything. At the moment you are sending to queries to the database (one for .Any() and one to get entities). If you do .ToList() you will send only one query that brings entities but .Any() would be called on the list not on the database so it will be much faster and there is no trip to the database. Also ToList() force query evaluation so your foreach loop will not keep the data reader open as it will iterate on the list.

Resources