Commit 88044970 authored by Thomas Huster's avatar Thomas Huster

[21097] replace query execute as stream with execute as cursor

parent eb975429
Pipeline #16346 passed with stages
in 4 minutes and 40 seconds
......@@ -6,6 +6,7 @@ import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
......@@ -38,15 +39,15 @@ public class AccountTransaction extends AbstractEntityWithId
@Convert(converter = BooleanCharacterConverterSafe.class)
protected boolean deleted = false;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "patientid")
private Kontakt patient;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rechnungsid")
private Invoice invoice;
@OneToOne()
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "zahlungsid")
private Zahlung zahlung;
......
......@@ -36,12 +36,8 @@ import ch.elexis.core.model.InvoiceState;
@NamedQuery(name = "Invoice.number", query = "SELECT i FROM Invoice i WHERE i.deleted = false AND i.number = :number")
@NamedQuery(name = "Invoice.from.to.paid.notempty", query = "SELECT i FROM Invoice i WHERE i.deleted = false "
+ "AND i.invoiceDate >= :from AND i.invoiceDate <= :to AND NOT (i.state = ch.elexis.core.model.InvoiceState.PAID AND i.amount = '0')")
@NamedQuery(name = "Invoice.from.to.paid.notempty.size", query = "SELECT COUNT(i.id) FROM Invoice i WHERE i.deleted = false "
+ "AND i.invoiceDate >= :from AND i.invoiceDate <= :to AND NOT (i.state = ch.elexis.core.model.InvoiceState.PAID AND i.amount = '0')")
@NamedQuery(name = "Invoice.from.to.mandator.paid.notempty", query = "SELECT i FROM Invoice i WHERE i.deleted = false "
+ "AND i.mandator = :mandator AND i.invoiceDate >= :from AND i.invoiceDate <= :to AND NOT (i.state = ch.elexis.core.model.InvoiceState.PAID AND i.amount = '0')")
@NamedQuery(name = "Invoice.from.to.mandator.paid.notempty.size", query = "SELECT COUNT(i.id) FROM Invoice i WHERE i.deleted = false "
+ "AND i.mandator = :mandator AND i.invoiceDate >= :from AND i.invoiceDate <= :to AND NOT (i.state = ch.elexis.core.model.InvoiceState.PAID AND i.amount = '0')")
public class Invoice extends AbstractEntityWithId
implements EntityWithId, EntityWithDeleted, EntityWithExtInfo {
......
......@@ -6,6 +6,7 @@ import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
......@@ -35,7 +36,7 @@ public class Zahlung extends AbstractEntityWithId implements EntityWithId, Entit
@Convert(converter = BooleanCharacterConverterSafe.class)
protected boolean deleted = false;
@ManyToOne()
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "rechnungsid")
private Invoice invoice;
......
......@@ -6,7 +6,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
......@@ -21,15 +20,18 @@ import javax.persistence.metamodel.SingularAttribute;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.queries.ScrollableCursor;
import org.slf4j.LoggerFactory;
import ch.elexis.core.jpa.entities.EntityWithDeleted;
import ch.elexis.core.jpa.entities.EntityWithId;
import ch.elexis.core.jpa.model.adapter.internal.PredicateGroupStack;
import ch.elexis.core.jpa.model.adapter.internal.PredicateHandler;
import ch.elexis.core.jpa.model.adapter.internal.QueryCursor;
import ch.elexis.core.model.ModelPackage;
import ch.elexis.core.services.IModelService;
import ch.elexis.core.services.IQuery;
import ch.elexis.core.services.IQueryCursor;
import ch.elexis.core.services.ISubQuery;
/**
......@@ -305,21 +307,13 @@ public abstract class AbstractModelQuery<T> implements IQuery<T> {
return query;
}
@SuppressWarnings("unchecked")
@Override
public Stream<T> executeAsStream(){
public IQueryCursor<T> executeAsCursor(){
TypedQuery<?> query = getTypedQuery();
query.setHint(QueryHints.MAINTAIN_CACHE, HintValues.FALSE);
Stream<T> ret = query.getResultStream().map(
e -> (T) adapterFactory.getModelAdapter((EntityWithId) e, clazz, true).orElse(null));
// detach and clear L1 cache
entityManager.clear();
return ret;
}
@Override
public long getSize(){
return getSizeQuery().getSingleResult();
query.setHint(QueryHints.SCROLLABLE_CURSOR, HintValues.TRUE);
ScrollableCursor cursor = (ScrollableCursor) query.getSingleResult();
return new QueryCursor<T>(cursor, adapterFactory, clazz);
}
@SuppressWarnings("unchecked")
......
......@@ -4,17 +4,19 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import org.eclipse.persistence.config.HintValues;
import org.eclipse.persistence.config.QueryHints;
import org.eclipse.persistence.queries.ScrollableCursor;
import org.slf4j.LoggerFactory;
import ch.elexis.core.jpa.entities.EntityWithId;
import ch.elexis.core.jpa.model.adapter.internal.QueryCursor;
import ch.elexis.core.services.INamedQuery;
import ch.elexis.core.services.IQueryCursor;
public class NamedQuery<R, T> implements INamedQuery<R> {
......@@ -24,6 +26,7 @@ public class NamedQuery<R, T> implements INamedQuery<R> {
private Class<? extends EntityWithId> entityClazz;
private TypedQuery<?> query;
private EntityManager entityManager;
public NamedQuery(Class<R> returnValueClazz, Class<T> interfaceClazz, boolean refreshCache,
AbstractModelAdapterFactory adapterFactory, EntityManager entityManager, String queryName){
......@@ -31,6 +34,7 @@ public class NamedQuery<R, T> implements INamedQuery<R> {
this.interfaceClazz = interfaceClazz;
this.returnValueClazz = returnValueClazz;
this.entityClazz = adapterFactory.getEntityClass(interfaceClazz);
this.entityManager = entityManager;
this.query = entityManager.createNamedQuery(queryName, entityClazz);
// update cache with results (https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Query_Hints)
......@@ -67,23 +71,20 @@ public class NamedQuery<R, T> implements INamedQuery<R> {
}
}
@SuppressWarnings("unchecked")
@Override
public Stream<R> executeAsStreamWithParameters(Map<String, Object> parameters){
public IQueryCursor<R> executeAsCursorWithParameters(Map<String, Object> parameters){
parameters.forEach((k, v) -> {
v = resolveValue(v);
query.setParameter(k, v);
});
query.setHint(QueryHints.MAINTAIN_CACHE, HintValues.FALSE);
query.setHint(QueryHints.SCROLLABLE_CURSOR, HintValues.TRUE);
if (returnValueClazz.equals(interfaceClazz)) {
Stream<R> ret = (Stream<R>) query
.getResultStream().map(e -> adapterFactory
.getModelAdapter((EntityWithId) e, interfaceClazz, true).orElse(null))
.filter(o -> o != null);
return ret;
ScrollableCursor cursor = (ScrollableCursor) query.getSingleResult();
return new QueryCursor<R>(cursor, adapterFactory, interfaceClazz);
} else {
// query result list can contain null values, we do not want to see them
return (Stream<R>) query.getResultStream().filter(r -> r != null);
ScrollableCursor cursor = (ScrollableCursor) query.getSingleResult();
return new QueryCursor<R>(cursor, null, null);
}
}
......
package ch.elexis.core.jpa.model.adapter.internal;
import java.util.Optional;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.queries.Cursor;
import org.eclipse.persistence.queries.ScrollableCursor;
import org.slf4j.LoggerFactory;
import ch.elexis.core.jpa.entities.EntityWithId;
import ch.elexis.core.jpa.model.adapter.AbstractModelAdapterFactory;
import ch.elexis.core.model.Identifiable;
import ch.elexis.core.services.IQueryCursor;
public class QueryCursor<T> implements IQueryCursor<T> {
private Cursor cursor;
private AbstractModelAdapterFactory adapterFactory;
private Class<?> interfaceClazz;
private boolean adapt;
public QueryCursor(ScrollableCursor cursor, AbstractModelAdapterFactory adapterFactory,
Class<?> interfaceClazz){
this.cursor = cursor;
this.adapterFactory = adapterFactory;
this.interfaceClazz = interfaceClazz;
this.adapt = adapterFactory != null && interfaceClazz != null;
}
@Override
public void close(){
try {
cursor.close();
} catch (DatabaseException e) {
LoggerFactory.getLogger(getClass()).error("Error closing cursor", e);
}
}
@Override
public boolean hasNext(){
return cursor.hasNext();
}
@SuppressWarnings("unchecked")
@Override
public T next(){
if (adapt) {
Optional<Identifiable> adapter =
adapterFactory.getModelAdapter((EntityWithId) cursor.next(), interfaceClazz, true);
return (T) adapter.get();
} else {
return (T) cursor.next();
}
}
@Override
public int size(){
return cursor.size();
}
@Override
public void clear(){
cursor.clear();
}
}
......@@ -194,14 +194,14 @@ public class Invoice extends AbstractIdDeleteModelAdapter<ch.elexis.core.jpa.ent
@Override
public List<IPayment> getPayments(){
CoreModelServiceHolder.get().refresh(this);
return getEntity().getPayments().parallelStream().filter(p -> !p.isDeleted())
return getEntity().getPayments().stream().filter(p -> !p.isDeleted())
.map(p -> ModelUtil.getAdapter(p, IPayment.class, true)).collect(Collectors.toList());
}
@Override
public List<IAccountTransaction> getTransactions(){
CoreModelServiceHolder.get().refresh(this);
return getEntity().getTransactions().parallelStream().filter(p -> !p.isDeleted())
return getEntity().getTransactions().stream().filter(p -> !p.isDeleted())
.map(p -> ModelUtil.getAdapter(p, IAccountTransaction.class, true))
.collect(Collectors.toList());
}
......
......@@ -3,6 +3,6 @@
<service>
<provide interface="ch.elexis.core.services.IAccountService"/>
</service>
<reference cardinality="1..1" field="configService" interface="ch.elexis.core.services.ConfigService" name="configService"/>
<reference cardinality="1..1" field="configService" interface="ch.elexis.core.services.IConfigService" name="configService"/>
<implementation class="ch.elexis.core.services.AccountService"/>
</scr:component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="ch.elexis.core.services.holder.AccountServiceHolder">
<reference bind="setAppointmentService" interface="ch.elexis.core.services.IAccountService" name="AppointmentService"/>
<reference bind="setAccountService" interface="ch.elexis.core.services.IAccountService" name="AccountService"/>
<implementation class="ch.elexis.core.services.holder.AccountServiceHolder"/>
</scr:component>
\ No newline at end of file
......@@ -20,7 +20,7 @@ public class AccountService implements IAccountService {
private HashMap<Integer, IAccount> localCache;
@Reference
private ConfigService configService;
private IConfigService configService;
private List<IAccount> loadAccounts(){
List<IAccount> ret = new ArrayList<>();
......
......@@ -7,7 +7,6 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.annotations.Component;
......@@ -310,8 +309,8 @@ public class InvoiceService implements IInvoiceService {
public List<IInvoice> getInvoices(IEncounter encounter){
INamedQuery<IInvoiceBilled> query =
CoreModelServiceHolder.get().getNamedQuery(IInvoiceBilled.class, "encounter");
Stream<IInvoiceBilled> invoicebilled =
query.executeAsStreamWithParameters(query.getParameterMap("encounter", encounter));
List<IInvoiceBilled> invoicebilled =
query.executeWithParameters(query.getParameterMap("encounter", encounter));
HashSet<IInvoice> uniqueInvoices = new HashSet<IInvoice>();
invoicebilled.forEach(ib -> uniqueInvoices.add(ib.getInvoice()));
return new ArrayList<IInvoice>(uniqueInvoices);
......
......@@ -10,7 +10,7 @@ public class AccountServiceHolder {
private static IAccountService accountService;
@Reference
public void setAppointmentService(IAccountService accountService){
public void setAccountService(IAccountService accountService){
AccountServiceHolder.accountService = accountService;
}
......
package ch.elexis.core.internal;
import ch.elexis.core.services.IQueryCursor;
public class EmptyCursor<T> implements IQueryCursor<T> {
@Override
public void close(){
}
@Override
public boolean hasNext(){
return false;
}
@Override
public T next(){
return null;
}
@Override
public int size(){
return 0;
}
@Override
public void clear(){
}
}
......@@ -4,7 +4,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
public interface INamedQuery<R> {
/**
......@@ -17,13 +16,13 @@ public interface INamedQuery<R> {
public List<R> executeWithParameters(Map<String, Object> parameters);
/**
* Execute the query with the provided parameters and return a stream with the resulting
* objects.
* Execute the query with the provided parameters and return a {@link IQueryCursor} with the
* resulting objects.
*
* @param parameters
* @return
*/
public Stream<R> executeAsStreamWithParameters(Map<String, Object> parameters);
public IQueryCursor<R> executeAsCursorWithParameters(Map<String, Object> parameters);
/**
* Execute the query and return a single result. If more than one result is available, a warning
......
......@@ -3,7 +3,6 @@ package ch.elexis.core.services;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.eclipse.emf.ecore.EStructuralFeature;
......@@ -117,15 +116,7 @@ public interface IQuery<T> {
*
* @return
*/
public Stream<T> executeAsStream();
/**
* Execute a count of the expected results of the query. Use to determine expected size of
* {@link #executeAsStream()}.
*
* @return
*/
public long getSize();
public IQueryCursor<T> executeAsCursor();
/**
* Execute the query and return a single result. If more than one result
......
package ch.elexis.core.services;
import java.io.Closeable;
import java.util.Iterator;
import ch.elexis.core.internal.EmptyCursor;
public interface IQueryCursor<T> extends Iterator<T>, Closeable {
@Override
public boolean hasNext();
@Override
public T next();
public int size();
public void clear();
public static <T> IQueryCursor<T> empty(){
return new EmptyCursor<>();
}
public void close();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment