Merge remote-tracking branch 'ebean/master' into pr/enh/softreference_in_cache

This commit is contained in:
Jonas Pöhler committed 2021-07-16 15:29:33 +02:00
commit 60ef87b269
246 files changed
+5518 -1538

No files matched your search

+17 -17
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.8.1-SNAPSHOT</version>
<version>12.10.0-SNAPSHOT</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -72,7 +72,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.6.1</version>
<version>12.9.4-RC1</version>
<scope>test</scope>
</dependency>
@@ -87,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.8.1-SNAPSHOT</version>
<version>12.10.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.8.1-SNAPSHOT</version>
<version>12.10.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.8.1-SNAPSHOT</version>
<version>12.10.0-SNAPSHOT</version>
</dependency>
<dependency>
@@ -119,6 +119,15 @@
<scope>provided</scope>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
and not require a separate module for it -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
@@ -173,7 +182,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.10</version>
<version>42.2.20</version>
<optional>true</optional>
</dependency>
@@ -214,15 +223,6 @@
<scope>test</scope>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
and not require a separate module for it -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
@@ -282,7 +282,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
<version>2.7</version>
<scope>test</scope>
</dependency>
@@ -302,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.6.6</version>
<version>12.9.1</version>
<executions>
<execution>
<id>test</id>
@@ -155,7 +155,6 @@ public class BindParams implements Serializable {
* Set an In Out parameter using position.
*/
public void setParameter(int position, Object value, int outType) {
Param p = getParam(position);
p.setInValue(value);
p.setOutType(outType);
@@ -178,8 +177,8 @@ public class BindParams implements Serializable {
* Using position set the In value of a parameter. Note that for nulls you
* must use setNullParameter.
*/
@SuppressWarnings("rawtypes")
public void setParameter(int position, Object value) {
Param p = getParam(position);
if (value instanceof Collection) {
// use of postgres ANY with positioned parameter
@@ -214,7 +213,6 @@ public class BindParams implements Serializable {
* Set a named In Out parameter.
*/
public void setParameter(String name, Object value, int outType) {
Param p = getParam(name);
p.setInValue(value);
p.setOutType(outType);
@@ -232,7 +230,6 @@ public class BindParams implements Serializable {
* Set a named In parameter that is not null.
*/
public Param setParameter(String name, Object value) {
Param p = getParam(name);
p.setInValue(value);
return p;
@@ -299,7 +296,6 @@ public class BindParams implements Serializable {
* Return true if the bind hash and count has not changed.
*/
public boolean isSameBindHash() {
if (bindHash == null) {
bindHash = calcQueryPlanHash();
return false;
@@ -330,10 +326,6 @@ public class BindParams implements Serializable {
private final StringBuilder preparedSql;
public OrderedList() {
this(new ArrayList<>());
}
public OrderedList(List<Param> paramList) {
this.paramList = paramList;
this.preparedSql = new StringBuilder();
@@ -1,52 +1,25 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.expression.IdInExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Used for bean cache lookup with where ids in expression.
* Process Cache lookup by Id(s).
*/
public class CacheIdLookup<T> {
private final IdInExpression idInExpression;
private int remaining;
public CacheIdLookup(IdInExpression idInExpression) {
this.idInExpression = idInExpression;
}
public interface CacheIdLookup<T> {
/**
* Return the Id values for the in expression.
* Return the Id values to lookup against the L2 cache.
*/
public Collection<?> idValues() {
return idInExpression.idValues();
}
Collection<?> idValues();
/**
* Process the hits returning the beans fetched from cache and
* adjusting the in expression (to not fetch the hits).
* Remove the hits returning the beans fetched from L2 cache.
*/
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
List<T> removeHits(BeanCacheResult<T> cacheResult);
Set<Object> hitIds = new HashSet<>();
List<T> beans = new ArrayList<>(hitIds.size());
for (BeanCacheResult.Entry<T> hit : cacheResult.hits()) {
hitIds.add(hit.getKey());
beans.add(hit.getBean());
}
this.remaining = idInExpression.removeIds(hitIds);
return beans;
}
public boolean allHits() {
return remaining == 0;
}
/**
* Return true if all beans where found in L2 cache.
*/
boolean allHits();
}
@@ -0,0 +1,55 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.expression.IdInExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Used for bean cache lookup with where ids in expression.
*/
public class CacheIdLookupMany<T> implements CacheIdLookup<T> {
private final IdInExpression idInExpression;
private int remaining;
public CacheIdLookupMany(IdInExpression idInExpression) {
this.idInExpression = idInExpression;
}
/**
* Return the Id values for the in expression.
*/
@Override
public Collection<?> idValues() {
return idInExpression.idValues();
}
/**
* Process the hits returning the beans fetched from cache and
* adjusting the in expression (to not fetch the hits).
*/
@Override
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
Set<Object> hitIds = new HashSet<>();
List<T> beans = new ArrayList<>(hitIds.size());
for (BeanCacheResult.Entry<T> hit : cacheResult.hits()) {
hitIds.add(hit.getKey());
beans.add(hit.getBean());
}
this.remaining = idInExpression.removeIds(hitIds);
return beans;
}
@Override
public boolean allHits() {
return remaining == 0;
}
}
@@ -0,0 +1,38 @@
package io.ebeaninternal.api;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Used for bean cache lookup with a single id value.
*/
public class CacheIdLookupSingle<T> implements CacheIdLookup<T> {
private final Object idValue;
private boolean found;
public CacheIdLookupSingle(Object idValue) {
this.idValue = idValue;
}
@Override
public Collection<?> idValues() {
return Collections.singleton(idValue);
}
@Override
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
final List<BeanCacheResult.Entry<T>> hits = cacheResult.hits();
if (hits.size() == 1) {
found = true;
return Collections.singletonList(hits.get(0).getBean());
}
return Collections.emptyList();
}
@Override
public boolean allHits() {
return found;
}
}
@@ -71,7 +71,6 @@ public class LoadManyRequest extends LoadRequest {
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
* generally makes sense to only fetch the Id values as the other property information is not
* used.
* </p>
*/
private boolean isOnlyIds() {
return onlyIds;
@@ -91,18 +90,16 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBatchSize();
}
private List<Object> getParentIdList() {
private List<Object> parentIdList(SpiEbeanServer server) {
List<Object> idList = new ArrayList<>();
BeanPropertyAssocMany<?> many = getMany();
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
bc.setLoader(server); // don't use the load buffer again
}
if (many.getTargetDescriptor().isPadInExpression()) {
BindPadding.padIds(idList);
}
return idList;
}
@@ -111,9 +108,7 @@ public class LoadManyRequest extends LoadRequest {
}
public SpiQuery<?> createQuery(SpiEbeanServer server) {
BeanPropertyAssocMany<?> many = getMany();
SpiQuery<?> query = many.newQuery(server);
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
@@ -124,11 +119,11 @@ public class LoadManyRequest extends LoadRequest {
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
query.where().raw(extraWhere.replace("${ta}", "t0"));
query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_"));
}
query.setLazyLoadForParents(many);
many.addWhereParentIdIn(query, getParentIdList(), loadContext.isUseDocStore());
many.addWhereParentIdIn(query, parentIdList(server), loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
@@ -146,7 +141,6 @@ public class LoadManyRequest extends LoadRequest {
// override to just select the Id values
query.select(many.getTargetIdProperty());
}
return query;
}
@@ -154,10 +148,8 @@ public class LoadManyRequest extends LoadRequest {
* After the query execution check for empty collections and load L2 cache if desired.
*/
public void postLoad() {
BeanDescriptor<?> desc = loadContext.getBeanDescriptor();
BeanPropertyAssocMany<?> many = getMany();
// check for BeanCollection's that where never processed
// in the +query or +lazy load due to no rows (predicates)
for (BeanCollection<?> bc : batch) {
@@ -172,6 +164,5 @@ public class LoadManyRequest extends LoadRequest {
desc.cacheManyPropPut(many, bc, parentId);
}
}
}
}
@@ -8,6 +8,11 @@ import java.util.List;
class NoopQueryPlanManager implements QueryPlanManager {
@Override
public void setDefaultThreshold(long thresholdMicros) {
// do nothing
}
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return SpiQueryBindCapture.NOOP;
@@ -12,6 +12,11 @@ public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager();
/**
* Update the global default threshold used when new query plans are created.
*/
void setDefaultThreshold(long thresholdMicros);
/**
* Create the bind capture for the given query plan.
*/
@@ -73,6 +73,10 @@ public class ScopeTrans {
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isFlushOnQuery();
}
Boolean autoPersistUpdates = txScope.getAutoPersistUpdates();
if (autoPersistUpdates != null) {
transaction.setAutoPersistUpdates(autoPersistUpdates);
}
if (txScope.isBatchSet()) {
transaction.setBatchMode(txScope.isBatchMode());
}
@@ -0,0 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.bean.EntityBean;
/**
* SPI interface for underlying BeanDescriptor.
*/
public interface SpiBeanType {
/**
* Return true if the bean contains a many property that has modifications.
* <p>
* That is a ManyToMany or a OneToMany with orphan removal with additions
* or removals from the collection.
*/
boolean isToManyDirty(EntityBean bean);
}
@@ -0,0 +1,13 @@
package io.ebeaninternal.api;
/**
* Manager of SpiBeanTypes.
*/
public interface SpiBeanTypeManager {
/**
* Return the bean type for the given entity class.
*/
SpiBeanType getBeanType(Class<?> entityType);
}
@@ -0,0 +1,26 @@
package io.ebeaninternal.api;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
/**
* Cancellable query, that has a delegate.
*
* @author Roland Praml, FOCONIS AG
*
*/
public interface SpiCancelableQuery extends CancelableQuery {
/**
* Checks if the query was cancelled.
* @throws PersistenceException if query was cancelled.
*/
void checkCancelled();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
}
@@ -1,14 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.ExtendedServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.CallOrigin;
import io.ebean.config.DatabaseConfig;
@@ -25,6 +17,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Service Provider extension to EbeanServer.
@@ -234,6 +227,11 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find single attribute streaming the result to a consumer.
*/
<T> void findSingleAttributeEach(SpiSqlQuery query, Class<T> cls, Consumer<T> consumer);
/**
* SqlQuery find one with mapper.
*/
@@ -249,6 +247,16 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findIterate query.
*/
<T> QueryIterator<T> findDtoIterate(SpiDtoQuery<T> query);
/**
* DTO findStream query.
*/
<T> Stream<T> findDtoStream(SpiDtoQuery<T> query);
/**
* DTO findList query.
*/
@@ -105,4 +105,9 @@ public interface SpiExpression extends Expression {
* Check for match to a natural key query returning false if it doesn't match.
*/
boolean naturalKey(NaturalKeyQueryData<?> data);
/**
* Apply property prefix when filterMany expressions included into main query.
*/
void prefixProperty(String path);
}
@@ -43,4 +43,9 @@ public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
default void applyRowLimits(SpiQuery<?> query) {
// do nothing by default
}
/**
* Apply property prefix when filterMany expressions included in main query.
*/
void prefixProperty(String path);
}
@@ -0,0 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.bean.PersistenceContext;
import java.util.List;
/**
* SPI extension to PersistenceContext.
*/
public interface SpiPersistenceContext extends PersistenceContext {
/**
* Return the list of dirty beans held by this persistence context.
*/
List<Object> dirtyBeans(SpiBeanTypeManager manager);
}
@@ -17,7 +17,6 @@ import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
@@ -31,7 +30,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes, SpiCancelableQuery {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -847,16 +846,6 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ReadEvent getFutureFetchAudit();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
/**
* Return true if this query has been cancelled.
*/
boolean isCancelled();
/**
* Return the base table to use if user defined on the query.
*/
@@ -1,6 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Set;
@@ -19,4 +20,8 @@ public interface SpiQueryFetch {
*/
void fetchProperties(String name, Set<String> properties, FetchConfig config);
/**
* Add a nested fetch graph.
*/
void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config);
}
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
/**
* SQL query binding (for SqlQuery and DtoQuery).
*/
public interface SpiSqlBinding {
public interface SpiSqlBinding extends SpiCancelableQuery {
/**
* Return the named or positioned parameters.
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.Transaction;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -147,6 +146,11 @@ public interface SpiTransaction extends Transaction {
*/
int depth();
/**
* Return true if dirty beans are automatically persisted.
*/
boolean isAutoPersistUpdates();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
@@ -191,7 +195,7 @@ public interface SpiTransaction extends Transaction {
* later. This is along the lines of 'extended persistence context'
* behaviour.
*/
PersistenceContext getPersistenceContext();
SpiPersistenceContext getPersistenceContext();
/**
* Set the persistence context to this transaction.
@@ -203,7 +207,7 @@ public interface SpiTransaction extends Transaction {
* and setPersistenceContext() enable a developer to reuse a single
* PersistenceContext with multiple transactions.
*/
void setPersistenceContext(PersistenceContext context);
void setPersistenceContext(SpiPersistenceContext context);
/**
* Return the underlying Connection for internal use.
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -43,6 +42,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.getLabel();
}
@Override
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
transaction.setAutoPersistUpdates(autoPersistUpdates);
}
@Override
public boolean isAutoPersistUpdates() {
return transaction.isAutoPersistUpdates();
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
@@ -369,12 +378,12 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return transaction.getPersistenceContext();
}
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
transaction.setPersistenceContext(context);
}
@@ -1,14 +1,10 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.CancelableQuery;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiSqlBinding;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.util.BindParamsParser;
@@ -17,11 +13,14 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
/**
* Wraps the objects involved in executing a SQL / Relational Query.
*/
public abstract class AbstractSqlQueryRequest {
public abstract class AbstractSqlQueryRequest implements CancelableQuery {
protected final SpiSqlBinding query;
@@ -41,6 +40,8 @@ public abstract class AbstractSqlQueryRequest {
protected long startNano;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the BeanFindRequest.
*/
@@ -48,6 +49,7 @@ public abstract class AbstractSqlQueryRequest {
this.server = server;
this.query = query;
this.transaction = (SpiTransaction) t;
this.query.setCancelableQuery(this);
}
/**
@@ -83,11 +85,6 @@ public abstract class AbstractSqlQueryRequest {
return transaction.isLogSql();
}
/**
* Set the resultSet and associated query plan if known.
*/
abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException;
/**
* Return the bindLog for this request.
*/
@@ -95,12 +92,15 @@ public abstract class AbstractSqlQueryRequest {
return bindLog;
}
/**
* Set the resultSet and associated query plan if known.
*/
abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException;
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
return resultSet.next();
}
public abstract boolean next() throws SQLException;
protected abstract void requestComplete();
@@ -113,7 +113,6 @@ public abstract class AbstractSqlQueryRequest {
JdbcClose.close(pstmt);
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
@@ -145,24 +144,30 @@ public abstract class AbstractSqlQueryRequest {
}
protected void executeAsSql(Binder binder) throws SQLException {
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
lock.lock();
try {
query.checkCancelled();
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
} finally {
lock.unlock();
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
query.checkCancelled();
}
/**
@@ -172,4 +177,13 @@ public abstract class AbstractSqlQueryRequest {
return sql;
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -0,0 +1,25 @@
package io.ebeaninternal.server.core;
import io.ebean.config.QueryPlanCapture;
import io.ebean.config.QueryPlanListener;
import io.ebean.meta.MetaQueryPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class DefaultQueryPlanListener implements QueryPlanListener {
static final QueryPlanListener INSTANT = new DefaultQueryPlanListener();
private static final Logger log = LoggerFactory.getLogger("io.ebean.QUERYPLAN");
@Override
public void process(QueryPlanCapture capture) {
// better to log this in JSON form?
String dbName = capture.getDatabase().getName();
for (MetaQueryPlan plan : capture.getPlans()) {
log.info("queryPlan db:{} label:{} queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}",
dbName, plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(),
plan.getSql(), plan.getBind(), plan.getPlan());
}
}
}
@@ -45,12 +45,7 @@ import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.bean.SingleBeanLoader;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.SlowQueryEvent;
import io.ebean.config.SlowQueryListener;
import io.ebean.config.TenantMode;
import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
@@ -125,6 +120,7 @@ import io.ebeanservice.docstore.api.DocStoreIntegration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -137,7 +133,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -145,6 +140,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -196,7 +192,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope;
private final int lazyLoadBatchSize;
private final int queryBatchSize;
private final boolean updateAllPropertiesInBatch;
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
@@ -217,7 +212,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
this.queryBatchSize = this.config.getQueryBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = this.config.getEncryptKeyManager();
@@ -242,7 +236,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this);
@@ -305,10 +299,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return lazyLoadBatchSize;
}
public int getQueryBatchSize() {
return queryBatchSize;
}
@Override
public Object currentTenantId() {
return currentTenantProvider == null ? null : currentTenantProvider.currentId();
@@ -410,6 +400,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
startQueryPlanCapture();
}
private void startQueryPlanCapture() {
if (config.isQueryPlanCapture()) {
long secs = config.getQueryPlanCapturePeriodSecs();
if (secs > 10) {
logger.info("capture query plan enabled, every {}secs", secs);
backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
}
}
}
private void collectQueryPlans() {
QueryPlanRequest request = new QueryPlanRequest();
request.setMaxCount(config.getQueryPlanCaptureMaxCount());
request.setMaxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
// obtains query explain plans ...
List<MetaQueryPlan> plans = metaInfoManager.queryPlanCollectNow(request);
QueryPlanListener listener = config.getQueryPlanListener();
if (listener == null) {
listener = DefaultQueryPlanListener.INSTANT;
}
listener.process(new QueryPlanCapture(this, plans));
}
@Override
@@ -1150,6 +1165,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction) {
return Optional.ofNullable(findOne(query, transaction));
@@ -1180,6 +1196,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> Set<T> findSet(Query<T> query, Transaction t) {
@@ -1196,6 +1213,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <K, T> Map<K, T> findMap(Query<T> query, Transaction t) {
@@ -1217,6 +1235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings("unchecked")
public <A, T> List<A> findSingleAttributeList(Query<T> query, Transaction t) {
@@ -1227,7 +1246,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
try {
request.initTransIfRequired();
return (List<A>) request.findSingleAttributeList();
return request.findSingleAttributeList();
} finally {
request.endTransIfRequired();
}
@@ -1277,6 +1296,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
return findIdsWithCopy(((SpiQuery<T>) query).copy(), t);
@@ -1337,29 +1357,32 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> FutureRowCount<T> findFutureCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<T>(this, copy, newTxn));
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> FutureIds<T> findFutureIds(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<T>(this, copy, newTxn));
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> FutureList<T> findFutureList(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
SpiQuery<T> spiQuery = (SpiQuery<T>) query.copy();
spiQuery.setFutureFetch(true);
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
@@ -1369,11 +1392,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<T>(this, spiQuery, newTxn));
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<>(this, spiQuery, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
@@ -1387,6 +1411,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new LimitOffsetPagedList<>(this, spiQuery);
}
@Nonnull
@Override
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
@@ -1399,39 +1424,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> Stream<T> findLargeStream(Query<T> query, Transaction transaction) {
return findStream(query, transaction);
}
@Nonnull
@Override
public <T> Stream<T> findStream(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, transaction);
try {
request.initTransIfRequired();
return toStream(request.findIterate());
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
return toStream(findIterate(query, transaction));
}
private <T> Stream<T> toStream(QueryIterator<T> queryIterator) {
return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false)
.onClose(new QueryIteratorClose(queryIterator));
}
private static class QueryIteratorClose implements Runnable {
private final QueryIterator<?> iterator;
private QueryIteratorClose(QueryIterator<?> iterator) {
this.iterator = iterator;
}
@Override
public void run() {
iterator.close();
}
return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false).onClose(queryIterator::close);
}
@Override
@@ -1470,6 +1476,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
}
@Nonnull
@Override
public <T> List<Version<T>> findVersions(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, transaction);
@@ -1481,6 +1488,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> List<T> findList(Query<T> query, Transaction t) {
return findList(query, t, false);
@@ -1540,6 +1548,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public List<SqlRow> findList(SqlQuery query, Transaction t) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
@@ -1576,6 +1585,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
}
@Override
public <T> void findSingleAttributeEach(SpiSqlQuery query, Class<T> cls, Consumer<T> consumer) {
executeSqlQuery((req) -> req.findSingleAttributeEach(cls, consumer), query);
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
@@ -1619,6 +1633,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> QueryIterator<T> findDtoIterate(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return request.findIterate();
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
}
@Override
public <T> Stream<T> findDtoStream(SpiDtoQuery<T> query) {
return toStream(findDtoIterate(query));
}
@Override
public <T> List<T> findDtoList(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
@@ -2241,6 +2272,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return checkUniqueness(bean, null);
}
@Nonnull
@Override
public Set<Property> checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
@@ -2252,14 +2284,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_getIntercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
// new.
// Primary Key is changeable only on new models - so skip check if we are not new
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
query.setId(id);
if (findCount(query, transaction) > 0) {
Set<Property> ret = new HashSet<>();
ret.add(idProperty);
return ret;
return Collections.singleton(idProperty);
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
@@ -2322,6 +2351,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
if (initRequest.isAll()) {
queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros());
}
return beanDescriptorManager.queryPlanInit(initRequest);
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.QueryIterator;
import io.ebean.core.type.DataReader;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -23,6 +24,9 @@ import java.util.function.Predicate;
*/
public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
private static final String ENC_PREFIX = EncryptAlias.PREFIX;
private static final String ENC_PREFIX_UPPER = EncryptAlias.PREFIX.toUpperCase();
private final SpiDtoQuery<T> query;
private final DtoQueryEngine queryEngine;
@@ -49,6 +53,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
ormQuery.setType(type);
ormQuery.setManualId();
query.setCancelableQuery(ormQuery);
// execute the underlying ORM query returning the ResultSet
SpiResultSet result = server.findResultSet(ormQuery, transaction);
this.pstmt = result.getStatement();
@@ -87,6 +92,11 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
}
}
public QueryIterator<T> findIterate() {
flushJdbcBatchOnQuery();
return queryEngine.findIterate(this);
}
public void findEach(Consumer<T> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
@@ -107,9 +117,13 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
return queryEngine.findList(this);
}
public boolean next() throws SQLException {
query.checkCancelled();
return dataReader.next();
}
@SuppressWarnings("unchecked")
public T readNextBean() throws SQLException {
dataReader.resetColumnPosition();
return (T) plan.readRow(dataReader);
}
@@ -133,9 +147,9 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
}
static String parseColumn(String columnLabel) {
if (columnLabel.startsWith("_e_") || columnLabel.startsWith("_E_")) {
if (columnLabel.startsWith(ENC_PREFIX) || columnLabel.startsWith(ENC_PREFIX_UPPER)) {
// encrypted column alias in the form _e_<tableAlias>_<column>
final int pos = columnLabel.indexOf("_", 3);
final int pos = columnLabel.indexOf("_", 4);
if (pos > -1) {
return columnLabel.substring(pos + 1);
}
@@ -0,0 +1,8 @@
package io.ebeaninternal.server.core;
/**
* Used to create column alias for encrypted columns.
*/
public interface EncryptAlias {
String PREFIX = "zx__";
}
@@ -423,13 +423,13 @@ public class InternalConfiguration {
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
@@ -617,10 +617,10 @@ public class InternalConfiguration {
}
public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) {
if (!config.isCollectQueryPlans()) {
if (!config.isQueryPlanEnable()) {
return QueryPlanManager.NOOP;
}
long threshold = config.getCollectQueryPlanThresholdMicros();
long threshold = config.getQueryPlanThresholdMicros();
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.getPlatform()), extraMetrics);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.CancelableQuery;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
@@ -35,7 +36,6 @@ import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -219,10 +219,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
*/
@Override
public void prepareQuery() {
secondaryQueries = query.convertJoins();
beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
queryPlanKey = query.prepare(this);
}
public boolean isNativeSql() {
@@ -379,9 +379,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void setBatched() {
batched = true;
if (type == Type.INSERT || type == Type.UPDATE) {
// used to trigger automatic jdbc batch flush
intercept.registerGetterCallback(this);
getterCallback = true;
if (beanDescriptor.hasSingleIdProperty()) {
// used to trigger automatic jdbc batch flush
intercept.registerGetterCallback(this);
getterCallback = true;
}
}
}
@@ -887,6 +889,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Remove deleted beans from the persistence context early.
*/
public void removeFromPersistenceContext() {
idValue = beanDescriptor.getId(entityBean);
beanDescriptor.contextDeleted(transaction.getPersistenceContext(), idValue);
}
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
@@ -1030,6 +1040,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
if (transaction.isAutoPersistUpdates() && idValue != null) {
// with getGeneratedKeys off we will not have a idValue
beanDescriptor.contextPut(transaction.getPersistenceContext(), idValue, entityBean);
}
}
/**
* Return if persist can be skipped on the reference only bean.
*/
public boolean isSkipReference() {
return intercept.isReference() || (Flags.isRecurse(flags) && beanDescriptor.referenceIdPropertyOnly(intercept));
}
public boolean isReference() {
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.persist.TrimLogSql;
@@ -20,17 +20,22 @@ public interface RelationalQueryEngine {
/**
* Find a list of beans using relational query.
*/
List<SqlRow> findList(RelationalQueryRequest request);
/**
* Find each query using relational query.
*/
void findEach(RelationalQueryRequest request, Consumer<SqlRow> consumer);
<T> List<T> findList(RelationalQueryRequest request, RowReader<T> reader);
/**
* Find each while query using relational query.
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
<T> void findEach(RelationalQueryRequest request, RowReader<T> reader, Predicate<T> consumer);
/**
* Find each via raw consumer.
*/
void findEach(RelationalQueryRequest request, RowConsumer mapper);
/**
* Find one via mapper.
*/
<T> T findOne(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find single attribute.
@@ -43,19 +48,9 @@ public interface RelationalQueryEngine {
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
/**
* Find one via mapper.
* Find single attribute streaming the result to a consumer.
*/
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find list via mapper.
*/
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find each via raw consumer.
*/
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
<T> void findSingleAttributeEach(RelationalQueryRequest request, Class<T> cls, Consumer<T> consumer);
/**
* Collect SQL query execution statistics.
@@ -1,10 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
import io.ebean.*;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlBinding;
@@ -57,18 +53,24 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
boolean findEachRow(RowConsumer mapper) {
flushJdbcBatchOnQuery();
queryEngine.findEachRow(this, mapper);
queryEngine.findEach(this, mapper);
return true;
}
<T> List<T> findListMapper(RowMapper<T> mapper) {
flushJdbcBatchOnQuery();
return queryEngine.findListMapper(this, mapper);
return queryEngine.findList(this, () -> mapper.map(resultSet, rows++));
}
<T> T findOneMapper(RowMapper<T> mapper) {
flushJdbcBatchOnQuery();
return queryEngine.findOneMapper(this, mapper);
return queryEngine.findOne(this, mapper);
}
public <T> boolean findSingleAttributeEach(Class<T> cls, Consumer<T> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findSingleAttributeEach(this, cls, consumer);
return true;
}
public <T> List<T> findSingleAttributeList(Class<T> cls) {
@@ -83,17 +85,17 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
public void findEach(Consumer<SqlRow> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
queryEngine.findEach(this, (resultSet, rowNum) -> consumer.accept(createNewRow()));
}
public void findEachWhile(Predicate<SqlRow> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
queryEngine.findEach(this, this::createNewRow, consumer);
}
public List<SqlRow> findList() {
flushJdbcBatchOnQuery();
return queryEngine.findList(this);
return queryEngine.findList(this, this::createNewRow);
}
/**
@@ -135,16 +137,15 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
return resultSet;
}
public void incrementRows() {
rows++;
}
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
List<T> list = new ArrayList<>();
while (next()) {
list.add(mapper.map(resultSet, rows++));
@Override
public boolean next() throws SQLException {
query.checkCancelled();
if (!resultSet.next()) {
return false;
} else {
rows++;
return true;
}
return list;
}
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.core;
import java.sql.SQLException;
/**
* Read a row building a result for that row.
*/
public interface RowReader<T> {
/**
* Build and return a result for a row.
*/
T read() throws SQLException;
}
@@ -38,15 +38,7 @@ import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
import io.ebean.plugin.Property;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.LoadBeanContext;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdatePlan;
import io.ebeaninternal.api.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
@@ -119,7 +111,7 @@ import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -1755,6 +1747,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return idProperty != null;
}
/**
* Return false for IdClass case with multiple @Id properties.
*/
public boolean hasSingleIdProperty() {
return idPropertyIndex != -1;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
@@ -1880,7 +1879,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
if (refBean == null) {
refBean = createReference(readOnly, false, id, pc);
}
return (EntityBean)refBean;
return (EntityBean) refBean;
}
/**
@@ -2017,6 +2016,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return owner.getBeanDescriptor(otherType);
}
/**
* Returns true, if the table is managed (i.e. an existing m2m relation).
*/
public boolean isTableManaged(String tableName) {
return owner.isTableManaged(tableName);
}
/**
* Return the order column property.
*/
@@ -2473,7 +2479,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the property path given the db table and column.
*/
public String findBeanPath(String schemaName, String tableName, String columnName) {
if (matchBaseTable(schemaName, tableName)) {
if (matchBaseTable(tableName)) {
return columnPath.get(columnName);
}
BeanPropertyAssoc<?> assocProperty = tablePath.get(tableName);
@@ -2489,10 +2495,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return null;
}
private boolean matchBaseTable(String schemaName, String tableName) {
boolean matchBaseTable(String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
|| baseTable.equalsIgnoreCase(schemaName + "." + tableName);
|| baseTable.endsWith("." + tableName);
}
/**
@@ -2937,6 +2943,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return false;
}
@Override
public boolean isToManyDirty(EntityBean bean) {
final EntityBeanIntercept ebi = bean._ebean_getIntercept();
for (BeanPropertyAssocMany<?> many : propertiesManySave) {
if (ebi.isLoadedProperty(many.getPropertyIndex())) {
final BeanCollection<?> value = (BeanCollection<?>) many.getValue(bean);
if (value != null && value.hasModifications()) {
return true;
}
}
}
return false;
}
/**
* Return true if the bean is draftable and considered a 'live' instance.
*/
@@ -3134,12 +3154,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return ebi.isReference() || referenceIdPropertyOnly(ebi);
}
boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
public boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
return idOnlyReference && ebi.hasIdOnly(idPropertyIndex);
}
public boolean isIdLoaded(EntityBeanIntercept ebi) {
return ebi.isLoadedProperty(idPropertyIndex);
// assume id loaded for IdClass case with idPropertyIndex == -1
return idPropertyIndex == -1 ? true : ebi.isLoadedProperty(idPropertyIndex);
}
boolean hasIdValue(EntityBean bean) {
@@ -3179,7 +3200,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
int propertyIndex = beanProperty.getPropertyIndex();
if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) {
Object value = beanProperty.getValue(ebi.getOwner());
if (value == null || beanProperty.isDirtyValue(value)) {
if (value != null && beanProperty.isDirtyValue(value)) {
// mutable scalar value which is considered dirty so mark
// it as such so that it is included in an update
ebi.markPropertyAsChanged(propertyIndex);
@@ -26,9 +26,7 @@ import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanType;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.core.InternString;
@@ -88,7 +86,7 @@ import java.util.concurrent.TimeUnit;
/**
* Creates BeanDescriptors.
*/
public class BeanDescriptorManager implements BeanDescriptorMap {
public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeManager {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
@@ -263,6 +261,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return descQueueMap.get(queueId);
}
@Override
public SpiBeanType getBeanType(Class<?> entityType) {
return getBeanDescriptor(entityType);
}
@Override
@SuppressWarnings("unchecked")
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
@@ -442,6 +445,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return tableToDescMap.get(tableName.toLowerCase());
}
@Override
public boolean isTableManaged(String tableName) {
return tableToDescMap.get(tableName.toLowerCase()) != null
|| tableToViewDescMap.get(tableName.toLowerCase()) != null;
}
/**
* Invalidate entity beans based on views via their dependent tables.
*/
@@ -76,4 +76,10 @@ public interface BeanDescriptorMap {
* Return true if Jackson core is present on the classpath.
*/
boolean isJacksonCorePresent();
/**
* Returns true, if the given table (or view) is managed by ebean
* (= an entity exists)
*/
boolean isTableManaged(String tableName);
}
@@ -18,6 +18,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.core.EncryptAlias;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
@@ -55,8 +56,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static io.ebean.util.StringHelper.replace;
/**
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
@@ -65,6 +64,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
private static final String ENC_PREFIX = " " + EncryptAlias.PREFIX;
/**
* Flag to mark this is the id property.
*/
@@ -353,13 +354,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
private String tableAliasIntern(BeanDescriptor<?> descriptor, String s, boolean dbEncrypted, String dbColumn) {
if (descriptor != null) {
s = replace(s, "${ta}.", "${}");
s = replace(s, "${ta}", "${}");
if (s != null && descriptor != null) {
s = s.replace("${ta}.", "${}");
s = s.replace("${ta}", "${}");
if (dbEncrypted) {
s = dbEncryptFunction.getDecryptSql(s);
String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn;
s = replace(s, "?", namedParam);
s = s.replace("?", namedParam);
}
}
return InternString.intern(s);
@@ -531,7 +532,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return the SQL for the column including decryption function and column alias.
*/
private String getDecryptSqlWithColumnAlias(String tableAlias) {
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + " _e_" + tableAlias + "_" + this.getDbColumn();
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + ENC_PREFIX + tableAlias + "_" + this.getDbColumn();
}
@Override
@@ -439,6 +439,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
boolean softDelete = targetDescriptor.isSoftDelete();
boolean needsX2Table = softDelete || getExtraWhere() != null;
StringBuilder sb = new StringBuilder(50);
SpiQuery<?> query = request.getQueryRequest().getQuery();
if (hasJoinTable()) {
@@ -446,7 +447,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
} else {
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
}
if (softDelete && hasJoinTable()) {
if (needsX2Table && hasJoinTable()) {
sb.append(" x join ");
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
sb.append(" x2 on ");
@@ -461,6 +462,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
exportedProperties[i].appendWhere(sb, "x.", path);
}
if (getExtraWhere() != null) {
sb.append(" and ");
if (hasJoinTable()) {
sb.append(getExtraWhere().replace("${ta}", "x2").replace("${mta}", "x"));
} else {
sb.append(getExtraWhere().replace("${ta}", "x"));
}
}
if (softDelete) {
String alias = hasJoinTable() ? "x2" : "x";
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias));
@@ -1061,4 +1072,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
/**
* Returns true, if we must create a m2m join table.
*/
public boolean createJoinTable() {
if (hasJoinTable() && getMappedBy() == null) {
// only create on other 'owning' side
return !descriptor.isTableManaged(intersectionJoin.getTable());
} else {
return false;
}
}
}
@@ -81,18 +81,18 @@ class BeanPropertyAssocManySqlHelp<T> {
@Override
public void visitOneImported(BeanPropertyAssocOne<?> p) {
// do nothing
}
@Override
public void visitScalar(BeanProperty p) {
public void visitScalar(BeanProperty p, boolean allowNonNull) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitEnd() {
// do nothing
}
}
@@ -10,7 +10,7 @@ public interface DbSqlContext {
/**
* Add a join to the sql query.
*/
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2);
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere);
/**
* Push the current table alias onto the stack.
@@ -34,6 +34,8 @@ public final class TableJoin {
private final int queryHash;
private final PropertyForeignKey foreignKey;
private final String extraWhere;
public TableJoin(DeployTableJoin deploy) {
this(deploy, null);
@@ -44,6 +46,7 @@ public final class TableJoin {
*/
public TableJoin(DeployTableJoin deploy, PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
this.extraWhere = deploy.getExtraWhere();
this.table = InternString.intern(deploy.getTable());
this.type = deploy.getType();
this.inheritInfo = deploy.getInheritInfo();
@@ -57,6 +60,7 @@ public final class TableJoin {
private TableJoin(TableJoin source, String overrideColumn) {
this.foreignKey = null;
this.extraWhere = source.extraWhere;
this.table = source.table;
this.type = source.type;
this.inheritInfo = source.inheritInfo;
@@ -146,7 +150,7 @@ public final class TableJoin {
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
String joinLiteral = joinType.getLiteral(type);
ctx.addJoin(joinLiteral, table, columns(), a1, a2);
ctx.addJoin(joinLiteral, table, columns(), a1, a2, extraWhere);
return joinType.autoToOuter(type);
}
@@ -86,6 +86,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* collection.
*/
public void setExtraWhere(String extraWhere) {
this.tableJoin.setExtraWhere(extraWhere);
this.extraWhere = extraWhere;
}
@@ -33,6 +33,8 @@ public class DeployTableJoin {
private ArrayList<DeployTableJoinColumn> columns = new ArrayList<>(4);
private InheritInfo inheritInfo;
private String extraWhere;
/**
* Create a DeployTableJoin.
@@ -137,6 +139,18 @@ public class DeployTableJoin {
this.type = type;
}
/**
* Returns the clause of an extra &#64;Where annotation.
* @return
*/
public String getExtraWhere() {
return extraWhere;
}
public void setExtraWhere(String extraWhere) {
this.extraWhere = extraWhere;
}
public DeployTableJoin createInverse(String tableName) {
DeployTableJoin inverse = new DeployTableJoin();
@@ -118,7 +118,7 @@ class AnnotationAssocManys extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
FetchPreference fetchPreference = get(prop, FetchPreference.class);
@@ -54,7 +54,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readAssocOne(DeployBeanPropertyAssocOne<?> prop) {
ManyToOne manyToOne = get(prop, ManyToOne.class);
if (manyToOne != null) {
readManyToOne(manyToOne, prop);
@@ -97,7 +96,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
// not expecting this to be used on assoc one properties
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class);
@@ -183,7 +182,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readManyToOne(ManyToOne propAnn, DeployBeanPropertyAssocOne<?> beanProp) {
setCascadeTypes(propAnn.cascade(), beanProp.getCascadeInfo());
setTargetType(propAnn.targetEntity(), beanProp);
setBeanTable(beanProp);
@@ -194,7 +192,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readOneToOne(OneToOne propAnn, DeployBeanPropertyAssocOne<?> prop) {
prop.setOneToOne();
prop.setDbInsertable(true);
prop.setDbUpdateable(true);
@@ -223,21 +220,19 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readPrimaryKeyJoin(PrimaryKeyJoinColumn primaryKeyJoin, DeployBeanPropertyAssocOne<?> prop) {
if (!prop.isOneToOne()) {
throw new IllegalStateException("Expecting property " + prop.getFullBeanName() + " with PrimaryKeyJoinColumn to be a OneToOne?");
}
prop.setPrimaryKeyJoin(true);
if (!primaryKeyJoin.name().isEmpty()) {
log.warn("Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name {} on {}", primaryKeyJoin.name(), prop.getFullBeanName());
log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - ignoring PrimaryKeyJoinColumn.name attribute [{}] on {}", primaryKeyJoin.name(), prop.getFullBeanName());
}
if (!primaryKeyJoin.referencedColumnName().isEmpty()) {
log.warn("Automatically determining join columns and Ignoring PrimaryKeyJoinColumn.referencedColumnName {} on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName());
log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - Ignoring PrimaryKeyJoinColumn.referencedColumnName attribute [{}] on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName());
}
BeanTable baseBeanTable = factory.getBeanTable(info.getDescriptor().getBeanType());
String localPrimaryKey = baseBeanTable.getIdColumn();
String foreignColumn = getBeanTable(prop).getIdColumn();
@@ -245,7 +240,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readEmbedded(DeployBeanPropertyAssocOne<?> prop, Embedded embedded) {
if (descriptor.isDocStoreOnly() && prop.getDocStoreDoc() == null) {
prop.setDocStoreEmbedded("");
}
@@ -257,7 +251,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
} catch (NoSuchMethodError e) {
// using standard JPA API without prefix option, maybe in EE container
}
readEmbeddedAttributeOverrides(prop);
}
@@ -153,7 +153,7 @@ public class AnnotationFields extends AnnotationParser {
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
initWhoProperties(prop);
@@ -334,7 +334,7 @@ public class AnnotationFields extends AnnotationParser {
}
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class);
@@ -129,4 +129,11 @@ public abstract class AnnotationParser extends AnnotationBase {
}
return columnNames;
}
/**
* Process any formula from &#64;Formula or &#64;Where.
*/
protected String processFormula(String source) {
return source == null ? null : source.replace("${dbTableName}", descriptor.getBaseTable());
}
}
@@ -44,9 +44,11 @@ public abstract class BaseTablePropertyVisitor implements BeanPropertyVisitor {
public abstract void visitOneImported(BeanPropertyAssocOne<?> p);
/**
* Override this method for normal scalar property.
* Override this method for normal scalar property. With allowNonNull
* used with properties of Embedded beans that can only be Nonnull when
* the Embedded bean is Nonnull.
*/
@Override
public abstract void visitScalar(BeanProperty p);
public abstract void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -40,8 +40,8 @@ public interface BeanPropertyVisitor {
void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded);
/**
* Visit a scalar property.
* Visit a scalar property specify allowing non-null (e.g. embedded bean scalar properties).
*/
void visitScalar(BeanProperty p);
void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -72,7 +72,7 @@ public class VisitProperties {
} else {
// simple scalar type
pv.visitScalar(p);
pv.visitScalar(p, true);
}
}
@@ -16,12 +16,17 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
public abstract class AbstractExpression implements SpiExpression {
protected final String propName;
protected String propName;
protected AbstractExpression(String propName) {
this.propName = propName;
}
@Override
public void prefixProperty(String path) {
this.propName = path + "." + propName;
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// by default can't use naturalKey cache
@@ -17,8 +17,8 @@ class BetweenPropertyExpression extends NonPrepareExpression {
private static final String BETWEEN = " between ";
private final String lowProperty;
private final String highProperty;
private String lowProperty;
private String highProperty;
private final Object value;
BetweenPropertyExpression(String lowProperty, String highProperty, Object value) {
@@ -27,6 +27,12 @@ class BetweenPropertyExpression extends NonPrepareExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
this.lowProperty = path + "." + lowProperty;
this.highProperty = path + "." + highProperty;
}
protected String name(String propName) {
return propName;
}
@@ -88,6 +88,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -126,6 +126,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public Junction<T> toJunction() {
return new JunctionExpression<>(Junction.Type.FILTER, this);
@@ -36,6 +36,11 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
this.subQuery = null;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -19,6 +19,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
throw new IllegalStateException("Not allowed?");
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeId(value);
@@ -66,6 +66,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
this.exprList = exprList;
}
@Override
public void prefixProperty(String path) {
exprList.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -57,6 +57,12 @@ abstract class LogicExpression implements SpiExpression {
this.expTwo = (SpiExpression) expTwo;
}
@Override
public void prefixProperty(String path) {
expOne.prefixProperty(path);
expTwo.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -24,6 +24,11 @@ class NestedPathWrapperExpression implements SpiExpression {
this.delegate = delegate;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -9,6 +9,11 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
abstract class NonPrepareExpression implements SpiExpression {
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -17,6 +17,11 @@ class NoopExpression implements SpiExpression {
protected static final NoopExpression INSTANCE = new NoopExpression();
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -22,6 +22,11 @@ final class NotExpression implements SpiExpression {
this.exp = (SpiExpression) exp;
}
@Override
public void prefixProperty(String path) {
exp.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -1,13 +0,0 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>AvajeLib</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Core services for an application.
<P>
Core services including Logging, Deployment properties and Background thread for
running frequent tasks.
</P>
</Body>
</HTML>
@@ -201,7 +201,6 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
@Override
public void loadMany(BeanCollection<?> bc, boolean onlyIds) {
lock.lock();
try {
boolean useCache = !onlyIds && context.hitCache && context.property.isUseCache();
@@ -215,6 +214,7 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
// find it using instance equality - avoiding equals() and potential deadlock issue
if (list.get(i) == bc) {
list.remove(i);
bc.setLoader(context.parent.getEbeanServer());
return;
}
}
@@ -222,10 +222,9 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
}
}
// Should reduce the list by checking each beanCollection in the L2 first before executing the query
LoadManyRequest req = new LoadManyRequest(this, onlyIds, useCache);
context.parent.getEbeanServer().loadMany(req);
context.parent.getEbeanServer().loadMany(new LoadManyRequest(this, onlyIds, useCache));
// clear the buffer as all entries have been loaded
list.clear();
} finally {
lock.unlock();
}
@@ -81,7 +81,7 @@ public final class BatchControl {
*/
private int bufferMax;
private Queue[] queues = new Queue[3];
private final Queue[] queues = new Queue[3];
/**
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
@@ -447,7 +447,7 @@ public final class DefaultPersister implements Persister {
public void insert(EntityBean bean, Transaction t) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.INSERT);
if (req.isReference()) {
if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
@@ -881,6 +881,7 @@ public final class DefaultPersister implements Persister {
}
int count = request.executeOrQueue();
request.removeFromPersistenceContext();
if (request.isPersistCascade()) {
deleteAssocOne(request);
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.persist.dml;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.BatchedPstmt;
import io.ebeaninternal.server.persist.BatchedPstmtHolder;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -1,14 +1,9 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.bean.NodeUsageListener;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebean.core.type.DataReader;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.util.JdbcClose;
@@ -19,12 +14,7 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanCollectionHelpFactory;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.deploy.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,11 +24,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -47,11 +33,9 @@ import java.util.concurrent.locks.ReentrantLock;
* The SqlSelect is based on a tree (Object Graph). The tree is traversed to see
* what parts are included in the tree according to the value of
* find.getInclude();
* </p>
* <p>
* The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is
* the key object used in reading the flat resultSet back into Objects.
* </p>
*/
public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTransactionEvent {
@@ -156,8 +140,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
*/
private PreparedStatement pstmt;
private boolean cancelled;
private String bindLog;
private final CQueryPlan queryPlan;
@@ -205,19 +187,15 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
this.query = request.getQuery();
this.queryMode = query.getMode();
this.lazyLoadManyProperty = query.getLazyLoadMany();
this.readOnly = request.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.getParentNode();
this.profilingListener = query.getProfilingListener();
this.autoTuneProfiling = profilingListener != null;
this.profilingListenerRef = autoTuneProfiling ? new WeakReference<>(profilingListener) : null;
// set the generated sql back to the query
// so its available to the user...
query.setGeneratedSql(queryPlan.getSql());
SqlTree sqlTree = queryPlan.getSqlTree();
this.rootNode = sqlTree.getRootNode();
this.manyProperty = sqlTree.getManyProperty();
@@ -293,15 +271,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
public void cancel() {
lock.lock();
try {
this.cancelled = true;
if (pstmt != null) {
try {
logger.debug("Cancelling query");
pstmt.cancel();
} catch (SQLException e) {
throw new PersistenceException("Error cancelling query", e);
}
}
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
@@ -333,9 +303,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
ResultSet prepareResultSet(boolean forwardOnlyHint) throws SQLException {
lock.lock();
try {
if (cancelled) {
throw new SQLException("Query cancelled");
}
// cancelled before we started
query.checkCancelled();
startNano = System.nanoTime();
SpiTransaction t = request.getTransaction();
profileOffset = t.profileOffset();
@@ -363,17 +332,18 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
bindLog = predicates.bind(queryPlan.bindEncryptedProperties(pstmt, conn));
return pstmt.executeQuery();
} finally {
lock.unlock();
}
ResultSet ret = pstmt.executeQuery();
query.checkCancelled();
return ret;
}
/**
* Close the resources.
* <p>
* The JDBC resultSet and statement need to be closed. Its important that this method is called.
* </p>
*/
public void close() {
try {
@@ -405,14 +375,12 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
@Override
public void setLazyLoadedChildBean(EntityBean bean, Object lazyLoadParentId) {
if (lazyLoadParentId != null) {
if (!lazyLoadParentId.equals(this.lazyLoadParentId)) {
// get the appropriate parent bean from the persistence context
this.lazyLoadParentBean = (EntityBean) lazyLoadManyProperty.getBeanDescriptor().contextGet(getPersistenceContext(), lazyLoadParentId);
this.lazyLoadParentId = lazyLoadParentId;
}
// add the loadedBean to the appropriate collection of lazyLoadParentBean
lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, bean, true);
}
@@ -423,10 +391,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* <p>
* If the query includes a many then the first object in the returned array is
* the one/master and the second the many/detail.
* </p>
*/
private boolean readNextBean() throws SQLException {
if (!moveToNextRow()) {
if (currentBean == null) {
nextBean = null;
@@ -440,7 +406,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
loadedBeanCount++;
if (manyProperty == null) {
// only single resultSet row required to build object so we are done
// read a single resultSet row into single bean
@@ -486,14 +451,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Return true if we can move to the next resultSet row.
*/
private boolean moveToNextRow() throws SQLException {
if (!dataReader.next()) {
noMoreRows = true;
return false;
}
rowCount++;
dataReader.resetColumnPosition();
return true;
}
@@ -502,7 +464,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
boolean readBean() throws SQLException {
boolean result = hasNext();
updateExecutionStatistics();
return result;
@@ -522,7 +483,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
boolean hasNext() throws SQLException {
lock.lock();
try {
if (noMoreRows || cancelled) {
query.checkCancelled();
if (noMoreRows) {
return false;
}
if (hasNextCache) {
@@ -539,20 +501,16 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Read version beans and their effective dates.
*/
List<Version<T>> readVersions() throws SQLException {
List<Version<T>> versionList = new ArrayList<>();
Version<T> version;
while ((version = readNextVersion()) != null) {
versionList.add(version);
}
updateExecutionStatistics();
return versionList;
}
private Version<T> readNextVersion() throws SQLException {
if (moveToNextRow()) {
return rootNode.loadVersion(this);
}
@@ -696,7 +654,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Should we create profileNodes for beans created in this query.
* <p>
* This is true for all queries except lazy load bean queries.
* </p>
*/
@Override
public boolean isAutoTuneProfiling() {
@@ -707,13 +664,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
private String getPath(String propertyName) {
if (currentPrefix == null) {
return propertyName;
} else if (propertyName == null) {
return currentPrefix;
}
String path = currentPathMap.get(propertyName);
if (path != null) {
return path;
@@ -724,7 +679,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
@Override
public void profileBean(EntityBeanIntercept ebi, String prefix) {
ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix);
ebi.setNodeUsageCollector(new NodeUsageCollector(node, profilingListenerRef));
}
@@ -749,7 +703,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* a find many query with read auditing so build the ReadEvent and log it.
*/
void auditFindMany() {
if (auditIds != null && !auditIds.isEmpty()) {
// get the id values of the underlying collection
ReadEvent futureReadEvent = query.getFutureFetchAudit();
@@ -787,7 +740,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Add the id to the audit id buffer and flush if needed in batches of 100.
*/
private void auditNextBean() {
if (auditIds == null) {
auditIds = new ArrayList<>(100);
}
@@ -9,7 +9,7 @@ import java.util.concurrent.locks.ReentrantLock;
class CQueryBindCapture implements SpiQueryBindCapture {
private static final double multiplier = 1.3d;
private static final double multiplier = 1.5d;
private final ReentrantLock lock = new ReentrantLock();
private final CQueryPlanManager manager;
@@ -616,11 +616,19 @@ class CQueryBuilder {
if (request.isInlineCountDistinct()) {
sb.append(")");
}
if (distinct && dbOrderBy != null && !query.isSingleAttribute()) {
if (distinct && dbOrderBy != null) {
// add the orderBy columns to the select clause (due to distinct)
final OrderBy<?> orderBy = query.getOrderBy();
if (orderBy != null && orderBy.supportsSelect()) {
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
String trimmed = DbOrderByTrim.trim(dbOrderBy);
if (query.isSingleAttribute() && trimmed.equals(select.getSelectSql())) {
// NOP, already in SQL
// TODO: what to do if we select("id").orderBy("prop,id")?
// Can we live with a query like "select t0.id, t0.prop, t0.id from"
// or should we elliminate the second "t0.id" from select
} else {
sb.append(", ").append(trimmed);
}
}
}
}
@@ -16,7 +16,7 @@ import io.ebeaninternal.server.core.DiffHelp;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.Binder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,11 +66,13 @@ public class CQueryEngine {
public <T> int delete(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(true, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
public <T> int update(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(false, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
@@ -97,6 +99,7 @@ public class CQueryEngine {
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -151,6 +154,7 @@ public class CQueryEngine {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -164,6 +168,7 @@ public class CQueryEngine {
public <T> int findCount(OrmQueryRequest<T> request) {
CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request);
request.setCancelableQuery(rcQuery);
try {
int count = rcQuery.findCount();
@@ -235,8 +240,10 @@ public class CQueryEngine {
} catch (SQLException e) {
try {
PersistenceException pex = cquery.createPersistenceException(e);
// create exception before closing connection
cquery.close();
throw cquery.createPersistenceException(e);
throw pex;
} finally {
request.rollbackTransIfRequired();
}
@@ -259,6 +266,7 @@ public class CQueryEngine {
// order by lower sys period desc
query.order().desc(sysPeriodLower);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
if (request.isLogSql()) {
@@ -327,6 +335,7 @@ public class CQueryEngine {
*/
public <T> SpiResultSet findResultSet(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
boolean fwdOnly;
if (request.isFindIterate()) {
@@ -411,6 +420,7 @@ public class CQueryEngine {
EntityBean bean = null;
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.CountedValue;
import io.ebean.core.type.ScalarDataReader;
import io.ebean.util.JdbcClose;
@@ -18,11 +19,12 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Base compiled query request for single attribute queries.
*/
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, CancelableQuery {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
@@ -65,6 +67,8 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
private final boolean containsCounts;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -111,7 +115,6 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
value = new CountedValue<>(value, dataReader.getLong());
}
result.add(value);
dataReader.resetColumnPosition();
rowCount++;
}
@@ -148,21 +151,27 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
}
private void prepareExecute() throws SQLException {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
lock.lock();
try {
query.checkCancelled();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery());
query.checkCancelled();
}
/**
@@ -195,4 +204,14 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -14,7 +14,7 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
private final CQuery<T> cquery;
private final OrmQueryRequest<T> request;
private boolean closed;
CQueryIteratorSimple(CQuery<T> cquery, OrmQueryRequest<T> request) {
@@ -54,8 +54,4 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
}
}
@Override
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -76,8 +76,4 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
}
}
@Override
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -13,7 +13,7 @@ import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.DataBindCapture;
@@ -30,14 +30,14 @@ public class CQueryPlanManager implements QueryPlanManager {
private final TransactionManager transactionManager;
private final long defaultThreshold;
private final QueryPlanLogger planLogger;
private final TimedMetric timeCollection;
private final TimedMetric timeBindCapture;
private long defaultThreshold;
public CQueryPlanManager(TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
this.transactionManager = transactionManager;
this.defaultThreshold = defaultThreshold;
@@ -46,6 +46,11 @@ public class CQueryPlanManager implements QueryPlanManager {
this.timeBindCapture = extraMetrics.getBindCapture();
}
@Override
public void setDefaultThreshold(long thresholdMicros) {
this.defaultThreshold = thresholdMicros;
}
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return new CQueryBindCapture(this, queryPlan, defaultThreshold);
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -13,11 +14,12 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the select row count query.
*/
class CQueryRowCount implements SpiProfileTransactionEvent {
class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -57,6 +59,8 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
private int rowCount;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -110,14 +114,22 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rset = pstmt.executeQuery();
query.checkCancelled();
if (!rset.next()) {
throw new PersistenceException("Expecting 1 row but got none?");
}
@@ -161,4 +173,14 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -10,11 +11,12 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the delete query.
* Executes the update query.
*/
class CQueryUpdate implements SpiProfileTransactionEvent {
class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -45,6 +47,8 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
*/
@@ -82,15 +86,22 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rowCount = pstmt.executeUpdate();
query.checkCancelled();
long executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
request.slowQueryCheck(executionTimeMicros, rowCount);
if (queryPlan.executionTime(executionTimeMicros)) {
@@ -122,4 +133,14 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getName(), rowCount, query.getProfileId());
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -11,11 +11,10 @@ import java.util.HashSet;
class DefaultDbSqlContext implements DbSqlContext {
private static final String COMMA = ", ";
private static final String PERIOD = ".";
private static final int STRING_BUILDER_INITIAL_CAPACITY = 140;
private static final String tableAliasPlaceHolder = "${ta}";
private static final String tableAliasManyPlaceHolder = "${mta}";
private final String columnAliasPrefix;
@@ -111,27 +110,22 @@ class DefaultDbSqlContext implements DbSqlContext {
}
@Override
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) {
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) {
if (tableJoins == null) {
tableJoins = new HashSet<>();
}
String joinKey = table + "-" + a1 + "-" + a2;
if (tableJoins.contains(joinKey)) {
return;
}
tableJoins.add(joinKey);
sb.append(" ").append(type);
boolean addAsOfOnClause = false;
if (draftSupport != null) {
appendTable(table, draftSupport.getDraftTable(table));
} else if (!historyQuery) {
sb.append(" ").append(table).append(" ");
} else {
// check if there is an associated history table and if so
// use the unionAll view - we expect an additional predicate to match
@@ -164,13 +158,17 @@ class DefaultDbSqlContext implements DbSqlContext {
if (addAsOfOnClause) {
sb.append(" and ").append(historySupport.getAsOfPredicate(a2));
}
if (extraWhere != null && !extraWhere.isEmpty()) {
sb.append(" and ");
// we will also need a many-table alias here
sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace(tableAliasManyPlaceHolder, a1));
}
}
private void appendTable(String table, String draftTable) {
if (draftTable != null) {
// there is an associated history table and view so use that
sb.append(" ").append(draftTable).append(" ");
} else {
sb.append(" ").append(table).append(" ");
}
@@ -193,7 +191,6 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public String getRelativePrefix(String propName) {
return currentPrefix == null ? propName : currentPrefix + "." + propName;
}
@@ -228,7 +225,6 @@ class DefaultDbSqlContext implements DbSqlContext {
// the same join has already been added.
return;
}
// we only want to add this join once
formulaJoins.add(converted);
sb.append(" ");
@@ -263,13 +259,10 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public void appendHistorySysPeriod() {
String tableAlias = tableAliasStack.peek();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodLower(tableAlias));
appendColumnAlias();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodUpper(tableAlias));
appendColumnAlias();
@@ -644,4 +644,9 @@ class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQueryFetch
public void fetchProperties(String property, Set<String> columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
@Override
public void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) {
detail.addNested(name, nestedDetail, config);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
@@ -10,10 +11,10 @@ import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.RelationalQueryEngine;
import io.ebeaninternal.server.core.RelationalQueryRequest;
import io.ebeaninternal.server.core.RowReader;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -59,12 +60,26 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer) {
public void findEach(RelationalQueryRequest request, RowConsumer consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
request.mapEach(consumer);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> void findEach(RelationalQueryRequest request, RowReader<T> reader, Predicate<T> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
if (!consumer.test(readRow(request))) {
if (!consumer.test(reader.read())) {
break;
}
}
@@ -79,25 +94,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public void findEach(RelationalQueryRequest request, Consumer<SqlRow> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
consumer.accept(readRow(request));
}
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
public <T> T findOne(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.BEAN);
T value = request.mapOne(mapper);
@@ -113,13 +110,15 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public <T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
public <T> List<T> findList(RelationalQueryRequest request, RowReader<T> reader) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<T> list = request.mapList(mapper);
List<T> rows = new ArrayList<>();
while (request.next()) {
rows.add(reader.read());
}
request.logSummary();
return list;
return rows;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
@@ -128,12 +127,19 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
@SuppressWarnings("unchecked")
@Override
public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) {
public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
try {
request.executeSql(binder, SpiQuery.Type.LIST);
request.mapEach(consumer);
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.getResultSet());
T value = null;
if (dataReader.next()) {
value = scalarType.read(dataReader);
}
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
@@ -147,68 +153,13 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
@Override
public <T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalarList(request, scalarType);
}
private <T> List<T> findScalarList(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
List<T> list = new ArrayList<>();
while (request.next()) {
request.incrementRows();
list.add(scalarType.read(binder.createDataReader(request.getResultSet())));
final DataReader dataReader = binder.createDataReader(request.getResultSet());
List<T> rows = new ArrayList<>();
while (dataReader.next()) {
rows.add(scalarType.read(dataReader));
}
request.logSummary();
return list;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalar(request, scalarType);
}
private <T> T findScalar(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
T value = null;
if (request.next()) {
request.incrementRows();
value = scalarType.read(binder.createDataReader(request.getResultSet()));
}
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public List<SqlRow> findList(RelationalQueryRequest request) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<SqlRow> rows = new ArrayList<>();
while (request.next()) {
rows.add(readRow(request));
}
request.logSummary();
return rows;
@@ -220,11 +171,23 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
/**
* Read the row from the ResultSet and return as a MapBean.
*/
private SqlRow readRow(RelationalQueryRequest request) throws SQLException {
return request.createNewRow();
}
@SuppressWarnings("unchecked")
@Override
public <T> void findSingleAttributeEach(RelationalQueryRequest request, Class<T> cls, Consumer<T> consumer) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.getResultSet());
while (dataReader.next()) {
consumer.accept(scalarType.read(dataReader));
}
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
}
@@ -1,10 +1,12 @@
package io.ebeaninternal.server.query;
import io.ebean.QueryIterator;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DtoQueryRequest;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -27,20 +29,29 @@ public class DtoQueryEngine {
}
return rows;
} catch (Throwable e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
public <T> QueryIterator<T> findIterate(DtoQueryRequest<T> request) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
return new DtoQueryIterator<>(request);
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
}
}
public <T> void findEach(DtoQueryRequest<T> request, Consumer<T> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
consumer.accept(request.readNextBean());
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
@@ -77,9 +88,8 @@ public class DtoQueryEngine {
break;
}
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.query;
import io.ebean.QueryIterator;
import io.ebeaninternal.server.core.DtoQueryRequest;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
class DtoQueryIterator<T> implements QueryIterator<T> {
private final DtoQueryRequest<T> request;
private boolean closed;
DtoQueryIterator(DtoQueryRequest<T> request) {
this.request = request;
}
@Override
public boolean hasNext() {
boolean result = false;
try {
result = request.next();
return result;
} catch (SQLException e) {
throw new PersistenceException(e);
} finally {
if (!result) {
close();
}
}
}
@Override
public T next() {
try {
return request.readNextBean();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
@Override
public void close() {
if (!closed) {
closed = true;
request.close();
request.endTransIfRequired();
}
}
}
@@ -5,6 +5,7 @@ import io.ebean.bean.EntityBean;
import io.ebean.core.type.ScalarType;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
@@ -130,6 +131,14 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
}
}
boolean oneToOneExported = false;
if (assocBeanProperty instanceof BeanPropertyAssocOne) {
BeanPropertyAssocOne<?> oneToOneProp = (BeanPropertyAssocOne<?>) assocBeanProperty;
if (oneToOneProp.isOneToOneExported()) {
oneToOneExported = true;
}
}
if (pathContainsMany) {
// "promote" to left join as the path contains a many
joinType = SqlJoinType.OUTER;
@@ -140,7 +149,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
assocBeanProperty.appendFrom(ctx, joinType);
}
joinType = assocBeanProperty.addJoin(joinType, prefix, ctx);
if (assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) {
if (!oneToOneExported && assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) {
ctx.append(" and ").append(assocBeanProperty.getSoftDeletePredicate(ctx.getTableAlias(prefix)));
}
}
@@ -47,16 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
ctx.popTableAlias();
}
/**
* append extraWhere to the join.
*/
@Override
protected SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) {
SqlJoinType join = super.appendFromAsJoin(ctx, joinType);
super.appendExtraWhere(ctx);
return join;
}
@Override
protected void appendExtraWhere(DbSqlContext ctx) {
// extraWhere is already appended to the tableJoin
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.querydefn;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
import io.ebeaninternal.api.SpiCancelableQuery;
/**
* Common code for Dto/Orm/RelationalQuery
*
* @author Roland Praml, FOCONIS AG
*
*/
public class AbstractQuery implements SpiCancelableQuery {
private boolean cancelled;
private CancelableQuery cancelableQuery;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled) {
cancelled = true;
if (cancelableQuery != null) {
cancelableQuery.cancel();
}
}
} finally {
lock.unlock();
}
}
@Override
public void checkCancelled() {
if (cancelled) {
throw new PersistenceException("Query was cancelled");
}
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
checkCancelled();
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.DtoQuery;
import io.ebean.ProfileLocation;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiDtoQuery;
@@ -15,11 +16,12 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Default implementation of DtoQuery.
*/
public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
public class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQuery<T> {
private final SpiEbeanServer server;
@@ -112,6 +114,16 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
server.findDtoEachWhile(this, consumer);
}
@Override
public QueryIterator<T> findIterate() {
return server.findDtoIterate(this);
}
@Override
public Stream<T> findStream() {
return server.findDtoStream(this);
}
@Override
public List<T> findList() {
return server.findDtoList(this);
@@ -35,6 +35,8 @@ import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.CacheIdLookup;
import io.ebeaninternal.api.CacheIdLookupMany;
import io.ebeaninternal.api.CacheIdLookupSingle;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
@@ -53,10 +55,10 @@ import io.ebeaninternal.server.deploy.BeanNaturalKey;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
@@ -79,7 +81,7 @@ import java.util.stream.Stream;
/**
* Default implementation of an Object Relational query.
*/
public class DefaultOrmQuery<T> implements SpiQuery<T> {
public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private static final String DEFAULT_QUERY_NAME = "default";
@@ -111,10 +113,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private ProfilingListener profilingListener;
private boolean cancelled;
private CancelableQuery cancelableQuery;
private Type type;
private String label;
@@ -537,6 +535,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (havingExpressions != null) {
havingExpressions.containsMany(beanDescriptor, manyWhereJoins);
}
if (orderBy != null) {
for (Property orderProperty : orderBy.getProperties()) {
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(orderProperty.getProperty());
if (elProp != null && elProp.containsFormulaWithJoin()) {
manyWhereJoins.addFormulaWithJoin(orderProperty.getProperty());
}
}
}
}
/**
@@ -740,10 +746,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return null;
}
List<SpiExpression> underlyingList = whereExpressions.getUnderlyingList();
if (underlyingList.size() == 1) {
if (underlyingList.isEmpty()) {
if (id != null) {
return new CacheIdLookupSingle<>(id);
}
} else if (underlyingList.size() == 1) {
SpiExpression singleExpression = underlyingList.get(0);
if (singleExpression instanceof IdInExpression) {
return new CacheIdLookup<>((IdInExpression) singleExpression);
return new CacheIdLookupMany<>((IdInExpression) singleExpression);
}
}
return null;
@@ -850,6 +860,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
copy.parentNode = parentNode;
copy.forUpdate = forUpdate;
copy.rawSql = rawSql;
setCancelableQuery(copy); // required to cancel findId query
return copy;
}
@@ -1232,6 +1243,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* Prepare the expressions (compile sub-queries etc).
*/
private void prepareExpressions(BeanQueryRequest<?> request) {
detail.prepareExpressions(request);
if (whereExpressions != null) {
whereExpressions.prepareExpression(request);
}
@@ -1412,6 +1424,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
detail.fetchProperties(path, other);
}
@Override
public void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) {
detail.addNested(name, nestedDetail, config);
}
@Override
public Query<T> select(String columns) {
detail.select(columns);
@@ -2034,16 +2051,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return futureFetchAudit;
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
@Override
public Query<T> setBaseTable(String baseTable) {
this.baseTable = baseTable;
@@ -2071,28 +2078,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return rootTableAlias != null ? rootTableAlias : defaultAlias;
}
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled && cancelableQuery != null) {
cancelled = true;
cancelableQuery.cancel();
}
} finally {
lock.unlock();
}
}
@Override
public boolean isCancelled() {
lock.lock();
try {
return cancelled;
} finally {
lock.unlock();
}
}
@Override
public Set<String> validate() {
@@ -17,15 +17,15 @@ import java.util.function.Predicate;
/**
* Default implementation of SQuery - SQL Query.
*/
public class DefaultRelationalQuery implements SpiSqlQuery {
public class DefaultRelationalQuery extends AbstractQuery implements SpiSqlQuery {
private static final long serialVersionUID = -1098305779779591068L;
private final transient SpiEbeanServer server;
private String label;
private final String query;
private String query;
private String label;
private int firstRow;
@@ -208,19 +208,25 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
return query;
}
<T> T mapperFindOne(RowMapper<T> mapper) {
private <T> T mapperFindOne(RowMapper<T> mapper) {
return server.findOneMapper(this, mapper);
}
<T> List<T> mapperFindList(RowMapper<T> mapper) {
private <T> List<T> mapperFindList(RowMapper<T> mapper) {
return server.findListMapper(this, mapper);
}
private <T> void mapperFindEach(RowMapper<T> mapper, Consumer<T> consumer) {
server.findEachRow(this, (resultSet, rowNum) -> consumer.accept(mapper.map(resultSet, rowNum)));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <T> TypeQuery<T> mapToScalar(Class<T> attributeType) {
return new Scalar(attributeType);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <T> TypeQuery<T> mapTo(RowMapper<T> mapper) {
return new Mapper(mapper);
@@ -248,6 +254,15 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
public List<T> findList() {
return findSingleAttributeList(type);
}
@Override
public void findEach(Consumer<T> consumer) {
scalarFindEach(type, consumer);
}
}
private <T> void scalarFindEach(Class<T> type, Consumer<T> consumer) {
server.findSingleAttributeEach(this, type, consumer);
}
private class Mapper<T> implements SqlQuery.TypeQuery<T> {
@@ -272,5 +287,10 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
public List<T> findList() {
return mapperFindList(mapper);
}
@Override
public void findEach(Consumer<T> consumer) {
mapperFindEach(mapper, consumer);
}
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.FetchConfig;
import io.ebean.event.BeanQueryRequest;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
@@ -9,25 +10,16 @@ import io.ebeaninternal.server.el.ElPropertyValue;
import javax.persistence.PersistenceException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Represents the internal structure of an Object Relational query.
* <p>
* Holds the select() and join() details of a ORM query.
* </p>
* <p>
* It is worth noting that for AutoTune a "tuned fetch info" builds an instance of OrmQueryDetail.
* Tuning a query is a matter of replacing an instance of this class with one that has been tuned
* with select() and join() set.
* </p>
*/
public class OrmQueryDetail implements Serializable {
@@ -81,7 +73,6 @@ public class OrmQueryDetail implements Serializable {
* Return true if equal in terms of autoTune (select and fetch without property ordering).
*/
public boolean isAutoTuneEqual(OrmQueryDetail otherDetail) {
if (!isSameByAutoTune(baseProps, otherDetail.baseProps)) {
return false;
}
@@ -109,7 +100,7 @@ public class OrmQueryDetail implements Serializable {
/**
* Return the detail in string form.
*/
public String asStringDebug() {
public String asString() {
StringBuilder sb = new StringBuilder();
if (baseProps.hasProperties()) {
baseProps.asStringDebug("select ", sb);
@@ -175,9 +166,7 @@ public class OrmQueryDetail implements Serializable {
}
private List<OrmQueryProperties> removeSecondaryQueries(boolean lazyQuery) {
ArrayList<String> matchingPaths = new ArrayList<>(2);
for (OrmQueryProperties chunk : fetchPaths.values()) {
boolean match = lazyQuery ? chunk.isLazyFetch() : chunk.isQueryFetch();
if (match) {
@@ -191,7 +180,6 @@ public class OrmQueryDetail implements Serializable {
// sort into depth order to remove
Collections.sort(matchingPaths);
// the list of secondary queries
ArrayList<OrmQueryProperties> props = new ArrayList<>();
@@ -225,14 +213,11 @@ public class OrmQueryDetail implements Serializable {
OrmQueryProperties chunk = getChunk(split[0], true);
chunk.addSecondaryQueryJoin(split[1]);
}
return props;
}
boolean tuneFetchProperties(OrmQueryDetail tunedDetail) {
boolean tuned = false;
OrmQueryProperties tunedRoot = tunedDetail.getChunk(null, false);
if (tunedRoot != null) {
tuned = true;
@@ -257,10 +242,7 @@ public class OrmQueryDetail implements Serializable {
}
/**
* Remove all joins and properties.
* <p>
* Typically for the row count query.
* </p>
* Remove all joins and properties. Typically for the row count query.
*/
public void clear() {
fetchPaths.clear();
@@ -306,7 +288,6 @@ public class OrmQueryDetail implements Serializable {
}
private void sortFetchPaths(BeanDescriptor<?> d, boolean addIds) {
if (!fetchPaths.isEmpty()) {
LinkedHashMap<String, OrmQueryProperties> sorted = new LinkedHashMap<>();
for (OrmQueryProperties p : fetchPaths.values()) {
@@ -317,7 +298,6 @@ public class OrmQueryDetail implements Serializable {
}
private void sortFetchPaths(BeanDescriptor<?> d, OrmQueryProperties p, LinkedHashMap<String, OrmQueryProperties> sorted, boolean addId) {
String path = p.getPath();
if (!sorted.containsKey(path)) {
String parentPath = p.getParentPath();
@@ -339,7 +319,6 @@ public class OrmQueryDetail implements Serializable {
parentProp = new OrmQueryProperties(parentPath, Collections.emptySet());
}
}
sortFetchPaths(d, parentProp, sorted, addId);
sorted.put(path, p);
}
@@ -350,14 +329,12 @@ public class OrmQueryDetail implements Serializable {
* Mark 'fetch joins' to 'many' properties over to 'query joins' where needed.
*/
void markQueryJoins(BeanDescriptor<?> beanDescriptor, String lazyLoadManyPath, boolean allowOne, boolean addIds) {
if (fetchPaths.isEmpty()) {
return;
}
// the name of the many fetch property if there is one
String manyFetchProperty = null;
// flag that is set once the many fetch property is chosen
boolean fetchJoinFirstMany = allowOne;
@@ -375,6 +352,7 @@ public class OrmQueryDetail implements Serializable {
// letting the first one remain a 'fetch join'
fetchJoinFirstMany = false;
manyFetchProperty = pair.getPath();
chunk.filterManyInline();
} else {
// convert this one over to a 'query join'
chunk.markForQueryJoin();
@@ -388,7 +366,6 @@ public class OrmQueryDetail implements Serializable {
* Sort the fetch entries taking into account fetchPreference on the path.
*/
private List<FetchEntry> sortByFetchPreference(BeanDescriptor<?> desc) {
List<FetchEntry> entries = new ArrayList<>(fetchPaths.size());
int idx = 0;
for (Map.Entry<String, OrmQueryProperties> entry : fetchPaths.entrySet()) {
@@ -452,11 +429,9 @@ public class OrmQueryDetail implements Serializable {
* </p>
*/
public void setDefaultSelectClause(BeanDescriptor<?> desc) {
if (desc.hasDefaultSelectClause() && !hasSelectClause()) {
baseProps = new OrmQueryProperties(null, desc.getDefaultSelectClause());
}
for (OrmQueryProperties joinProps : fetchPaths.values()) {
if (!joinProps.hasSelectClause()) {
BeanDescriptor<?> assocDesc = desc.getBeanDescriptor(joinProps.getPath());
@@ -506,7 +481,6 @@ public class OrmQueryDetail implements Serializable {
props = new OrmQueryProperties(path);
fetch(props);
return props;
} else {
return props;
}
@@ -516,9 +490,7 @@ public class OrmQueryDetail implements Serializable {
* Return true if the fetch path is included.
*/
public boolean includesPath(String path) {
OrmQueryProperties chunk = fetchPaths.get(path);
// may not have fetch properties if just +cache etc
return chunk != null && !chunk.isCache();
}
@@ -537,6 +509,15 @@ public class OrmQueryDetail implements Serializable {
return fetchPaths.entrySet();
}
/**
* Prepare filterMany expressions that are being included into the main query.
*/
public void prepareExpressions(BeanQueryRequest<?> request) {
for (OrmQueryProperties value : fetchPaths.values()) {
value.prepareExpressions(request);
}
}
private static class FetchEntry implements Comparable<FetchEntry> {
private final int index;
@@ -4,6 +4,7 @@ import io.ebean.ExpressionFactory;
import io.ebean.FetchConfig;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebean.event.BeanQueryRequest;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionFactory;
@@ -120,8 +121,8 @@ public class OrmQueryProperties implements Serializable {
this.parentPath = SplitName.parent(path);
this.allProperties = other.allProperties;
this.included = other.included;
this.cache = other.cache;
this.fetchConfig = fetchConfig;
this.cache = fetchConfig.isCache();
}
/**
@@ -178,8 +179,6 @@ public class OrmQueryProperties implements Serializable {
SpiExpressionFactory queryEf = (SpiExpressionFactory) rootQuery.getExpressionFactory();
ExpressionFactory filterEf = queryEf.createExpressionFactory();// exprPath);
filterMany = new FilterExpressionList(exprPath, filterEf, rootQuery);
// by default we need to make this a 'query join' now
markForQueryJoin = true;
}
return filterMany;
}
@@ -194,6 +193,24 @@ public class OrmQueryProperties implements Serializable {
return filterMany.trimPath(trimPath);
}
/**
* Adjust filterMany expressions for inclusion in main query.
*/
public void filterManyInline() {
if (filterMany != null){
filterMany.prefixProperty(path);
}
}
/**
* Prepare filterMany expressions for query plan key.
*/
public void prepareExpressions(BeanQueryRequest<?> request) {
if (filterMany != null) {
filterMany.prepareExpression(request);
}
}
/**
* Return the filterMany expression list (can be null).
*/
@@ -206,7 +223,6 @@ public class OrmQueryProperties implements Serializable {
*/
public void setFilterMany(SpiExpressionList<?> filterMany) {
this.filterMany = filterMany;
this.markForQueryJoin = true;
}
/**
@@ -1,11 +1,12 @@
package io.ebeaninternal.server.transaction;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiBeanType;
import io.ebeaninternal.api.SpiBeanTypeManager;
import io.ebeaninternal.api.SpiPersistenceContext;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -13,19 +14,16 @@ import java.util.concurrent.locks.ReentrantLock;
* <p>
* Ensures only one instance of a bean is used according to its type and unique
* id.
* </p>
* <p>
* PersistenceContext lives on a Transaction and as such is expected to only
* have a single thread accessing it at a time. This is not expected to be used
* concurrently.
* </p>
* <p>
* Duplicate beans are ones having the same type and unique id value. These are
* considered duplicates and replaced by the bean instance that was already
* loaded into the PersistenceContext.
* </p>
*/
public final class DefaultPersistenceContext implements PersistenceContext {
public final class DefaultPersistenceContext implements SpiPersistenceContext {
/**
* Map used hold caches. One cache per bean type.
@@ -200,6 +198,20 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
}
@Override
public List<Object> dirtyBeans(SpiBeanTypeManager manager) {
lock.lock();
try {
List<Object> list = new ArrayList<>();
for (ClassContext classContext : typeCache.values()) {
classContext.dirtyBeans(manager, list);
}
return list;
} finally {
lock.unlock();
}
}
@Override
public String toString() {
lock.lock();
@@ -211,26 +223,26 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
private ClassContext getClassContext(Class<?> rootType) {
return typeCache.computeIfAbsent(rootType, k -> new ClassContext());
return typeCache.computeIfAbsent(rootType, k -> new ClassContext(rootType));
}
private static class ClassContext {
private final Map<Object, Object> map = new HashMap<>();
private final Class<?> rootType;
private Set<Object> deleteSet;
private int initialSize;
private ClassContext parent;
private ClassContext() {
private ClassContext(Class<?> rootType) {
this.rootType = rootType;
}
/**
* Create as a shallow copy.
*/
private ClassContext(ClassContext source, boolean initial) {
this.rootType = source.rootType;
if (initial || source.isTransfer()) {
parent = source.transferParent();
initialSize = parent.size();
@@ -318,6 +330,19 @@ public final class DefaultPersistenceContext implements PersistenceContext {
deleteSet.add(id);
map.remove(id);
}
/**
* Add the dirty beans to the list.
*/
void dirtyBeans(SpiBeanTypeManager manager, List<Object> list) {
final SpiBeanType beanType = manager.getBeanType(rootType);
for (Object value : map.values()) {
EntityBean bean = (EntityBean) value;
if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) {
list.add(value);
}
}
}
}
}
@@ -3,13 +3,9 @@ package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
@@ -58,7 +54,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
/**
* Holder of the objects fetched to ensure unique objects are used.
*/
private PersistenceContext persistenceContext;
private SpiPersistenceContext persistenceContext;
private Object tenantId;
@@ -95,6 +91,16 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
return startNanos;
}
@Override
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
// do nothing
}
@Override
public boolean isAutoPersistUpdates() {
return false;
}
@Override
public void setLabel(String label) {
// do nothing
@@ -411,7 +417,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -424,7 +430,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
@@ -3,20 +3,16 @@ package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.util.Str;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,12 +21,7 @@ import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
/**
@@ -99,7 +90,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
/**
* Holder of the objects fetched to ensure unique objects are used.
*/
private PersistenceContext persistenceContext;
private SpiPersistenceContext persistenceContext;
/**
* Used to give developers more control over the insert update and delete
@@ -187,6 +178,8 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private final long startNanos;
private boolean autoPersistUpdates;
/**
* Create a new JdbcTransaction.
*/
@@ -209,11 +202,12 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.batchOnCascadeMode = false;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.autoPersistUpdates = explicit && manager.isAutoPersistUpdates();
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
this.batchMode = manager.getPersistBatch();
this.batchOnCascadeMode = manager.getPersistBatchOnCascade();
this.batchMode = manager.isPersistBatch();
this.batchOnCascadeMode = manager.isPersistBatchOnCascade();
this.onQueryOnly = manager.getOnQueryOnly();
}
@@ -294,6 +288,17 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return sb.toString();
}
@Override
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
this.autoPersistUpdates = autoPersistUpdates;
this.batchMode = true;
}
@Override
public boolean isAutoPersistUpdates() {
return autoPersistUpdates;
}
@Override
public boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
@@ -774,6 +779,10 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
if (autoPersistUpdates) {
// Experimental - flush dirty beans held by the persistence context
manager.flushTransparent(persistenceContext, this);
}
batchFlush();
if (deferredList != null) {
for (PersistDeferredRelationship deferred : deferredList) {
@@ -788,7 +797,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -798,10 +807,9 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* This could be considered similar to EJB3 Extended PersistanceContext. In
* that you get the PersistanceContext from a transaction, hold onto it, and
* then set it back later to a second transaction.
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
@@ -933,11 +941,13 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
private void connectionEndForQueryOnly() {
try {
withEachCallback(TransactionCallback::preCommit);
if (onQueryOnly == OnQueryOnly.COMMIT) {
performCommit();
} else {
performRollback();
}
withEachCallback(TransactionCallback::postCommit);
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
}
@@ -1042,7 +1052,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
throw new IllegalStateException(illegalStateMessage);
}
try {
if (queryOnly) {
if (queryOnly && !autoPersistUpdates) {
connectionEndForQueryOnly();
} else {
flushCommitAndNotify();
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiPersistenceContext;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
@@ -25,6 +25,16 @@ class NoTransaction implements SpiTransaction {
static final NoTransaction INSTANCE = new NoTransaction();
@Override
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
// do nothing
}
@Override
public boolean isAutoPersistUpdates() {
return false;
}
@Override
public void setLabel(String label) {
// do nothing
@@ -339,12 +349,12 @@ class NoTransaction implements SpiTransaction {
}
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return null;
}
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
}
@Override
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionProxy;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -17,15 +17,8 @@ import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
@@ -51,7 +44,6 @@ import java.util.concurrent.atomic.AtomicLong;
* Manages transactions.
* <p>
* Keeps the Cache and Cluster in sync when transactions are committed.
* </p>
*/
public class TransactionManager implements SpiTransactionManager {
@@ -59,6 +51,8 @@ public class TransactionManager implements SpiTransactionManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
private final SpiServer server;
private final BeanDescriptorManager beanDescriptorManager;
/**
@@ -100,6 +94,8 @@ public class TransactionManager implements SpiTransactionManager {
*/
final DocStoreUpdateProcessor docStoreUpdateProcessor;
private final boolean autoPersistUpdates;
private final boolean persistBatch;
private final boolean persistBatchOnCascade;
@@ -153,7 +149,7 @@ public class TransactionManager implements SpiTransactionManager {
* Create the TransactionManager
*/
public TransactionManager(TransactionManagerOptions options) {
this.server = options.server;
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.txnDebug = txnLogger.isDebug();
@@ -161,6 +157,7 @@ public class TransactionManager implements SpiTransactionManager {
this.supportsSavepointId = databasePlatform.isSupportsSavepointId();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
this.autoPersistUpdates = options.config.isAutoPersistUpdates();
this.persistBatch = PersistBatch.ALL == options.config.getPersistBatch();
this.persistBatchOnCascade = PersistBatch.ALL == options.config.appliedPersistBatchOnCascade();
this.rollbackOnChecked = options.config.isTransactionRollbackOnChecked();
@@ -191,8 +188,6 @@ public class TransactionManager implements SpiTransactionManager {
this.txnMain = metricFactory.createTimedMetric("txn.main");
this.txnReadOnly = metricFactory.createTimedMetric("txn.readonly");
this.txnNamed = metricFactory.createTimedMetricMap("txn.named.");
// Add gauges for db pool size
scopeManager.register(this);
}
@@ -279,11 +274,15 @@ public class TransactionManager implements SpiTransactionManager {
return bulkEventListenerMap;
}
boolean getPersistBatch() {
boolean isAutoPersistUpdates() {
return autoPersistUpdates;
}
boolean isPersistBatch() {
return persistBatch;
}
public boolean getPersistBatchOnCascade() {
boolean isPersistBatchOnCascade() {
return persistBatchOnCascade;
}
@@ -293,20 +292,16 @@ public class TransactionManager implements SpiTransactionManager {
* There is a potential optimisation available when read committed is the default
* isolation level. If it is, then Connections used only for queries do not require
* commit or rollback but instead can just be put back into the pool via close().
* </p>
* <p>
* If the Isolation level is higher (say SERIALIZABLE) then Connections used
* just for queries do need to be committed or rollback after the query.
* </p>
*/
OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly) {
// first check for a system property 'override'
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
if (systemPropertyValue != null) {
return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
}
// default to rollback if not defined on the platform
return dbPlatformOnQueryOnly == null ? OnQueryOnly.COMMIT : dbPlatformOnQueryOnly;
}
@@ -348,9 +343,7 @@ public class TransactionManager implements SpiTransactionManager {
* Wrap an externally supplied Connection with a known transaction id.
*/
private SpiTransaction wrapExternalConnection(String id, Connection c) {
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
// set the default batch mode
t.setBatchMode(persistBatch);
t.setBatchOnCascade(persistBatchOnCascade);
@@ -398,7 +391,6 @@ public class TransactionManager implements SpiTransactionManager {
*/
@Override
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
try {
if (txnLogger.isDebug()) {
String msg = transaction.getLogPrefix() + "Rollback";
@@ -407,7 +399,6 @@ public class TransactionManager implements SpiTransactionManager {
}
txnLogger.debug(msg);
}
} catch (Exception ex) {
logger.error("Error while notifying TransactionEventListener of rollback event", ex);
}
@@ -434,7 +425,6 @@ public class TransactionManager implements SpiTransactionManager {
}
private void formatThrowable(Throwable e, StringBuilder sb) {
sb.append(e.toString());
StackTraceElement[] stackTrace = e.getStackTrace();
if (stackTrace.length > 0) {
@@ -457,11 +447,9 @@ public class TransactionManager implements SpiTransactionManager {
if (txnLogger.isDebug()) {
txnLogger.debug(transaction.getLogPrefix() + "Commit");
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
postCommit.notifyLocalCache();
backgroundExecutor.execute(postCommit.backgroundNotify());
} catch (Exception ex) {
logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex);
}
@@ -477,7 +465,6 @@ public class TransactionManager implements SpiTransactionManager {
}
private void externalModificationEvent(TransactionEventTable tableEvents) {
TransactionEvent event = new TransactionEvent();
event.add(tableEvents);
@@ -490,25 +477,21 @@ public class TransactionManager implements SpiTransactionManager {
* Notify local BeanPersistListeners etc of events from another server in the cluster.
*/
public void remoteTransactionEvent(RemoteTransactionEvent remoteEvent) {
if (clusterLogger.isDebugEnabled()) {
clusterLogger.debug("processing {}", remoteEvent);
}
CacheChangeSet changeSet = new CacheChangeSet();
RemoteTableMod tableMod = remoteEvent.getRemoteTableMod();
if (tableMod != null) {
changeSet.addInvalidate(tableMod.getTables());
}
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
if (tableIUDList != null) {
for (TableIUD tableIUD : tableIUDList) {
beanDescriptorManager.cacheNotify(tableIUD, changeSet);
}
}
// note DeleteById is written as BeanPersistIds and getBeanPersistList()
// processes both Bean IUD and DeleteById
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
@@ -517,7 +500,6 @@ public class TransactionManager implements SpiTransactionManager {
persistIds.notifyCache(changeSet);
}
}
changeSet.apply();
}
@@ -532,10 +514,8 @@ public class TransactionManager implements SpiTransactionManager {
* Prepare and then send/log the changeSet.
*/
void sendChangeLog(final ChangeSet changeSet) {
// can set userId, userIpAddress & userContext if desired
if (changeLogPrepare.prepare(changeSet)) {
if (changeLogAsync) {
// call the log method in background
backgroundExecutor.execute(() -> changeLogListener.log(changeSet));
@@ -642,9 +622,7 @@ public class TransactionManager implements SpiTransactionManager {
* Begin a scoped transaction.
*/
public ScopedTransaction beginScopedTransaction(TxScope txScope) {
txScope = initTxScope(txScope);
ScopedTransaction txnContainer = getActiveScoped();
boolean setToScope;
@@ -667,7 +645,6 @@ public class TransactionManager implements SpiTransactionManager {
if (nestedSavepoint && (type == TxType.REQUIRED || type == TxType.REQUIRES_NEW)) {
createTransaction = true;
transaction = createSavepoint(transaction, this);
} else {
createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
@@ -737,32 +714,26 @@ public class TransactionManager implements SpiTransactionManager {
* Determine whether to create a new transaction or not.
* <p>
* This will also potentially throw exceptions for MANDATORY and NEVER types.
* </p>
*/
private boolean isCreateNewTransaction(SpiTransaction current, TxType type) {
switch (type) {
case REQUIRED:
case SUPPORTS:
return current == null;
case REQUIRES_NEW:
return true;
case MANDATORY:
if (current == null) {
throw new PersistenceException("Transaction missing when MANDATORY");
}
return false;
case NEVER:
if (current != null) {
throw new PersistenceException("Transaction exists for Transactional NEVER");
}
return true; // always use NoTransaction instance
case NOT_SUPPORTED:
return true; // always use NoTransaction instance
default:
throw new RuntimeException("Should never get here?");
}
@@ -786,4 +757,14 @@ public class TransactionManager implements SpiTransactionManager {
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
/**
* Experimental - find dirty beans in the persistence context and persist them.
*/
public void flushTransparent(SpiPersistenceContext persistenceContext, SpiTransaction transaction) {
List<Object> dirtyBeans = persistenceContext.dirtyBeans(beanDescriptorManager);
if (!dirtyBeans.isEmpty()) {
server.updateAll(dirtyBeans, transaction);
}
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.DatabaseConfig;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
@@ -15,6 +16,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
*/
public class TransactionManagerOptions {
final SpiServer server;
final boolean notifyL2CacheInForeground;
final DatabaseConfig config;
final ClusterManager clusterManager;
@@ -31,11 +33,11 @@ public class TransactionManagerOptions {
final ClockService clockService;
public TransactionManagerOptions(boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
public TransactionManagerOptions(SpiServer server, boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify, ClockService clockService) {
this.server = server;
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
this.scopeManager = scopeManager;
@@ -1,26 +0,0 @@
package io.ebeaninternal.server.type;
import io.ebean.ModifyAwareType;
/**
* Check dirty state of json value which might be modify aware.
*/
class CheckMarkedDirty {
/**
* Return true if the value should be considered dirty (and included in an update).
*/
static boolean isDirty(Object value) {
if (value instanceof ModifyAwareType) {
ModifyAwareType modifyAware = (ModifyAwareType) value;
if (modifyAware.isMarkedDirty()) {
// reset the dirty state (consider not dirty after update)
modifyAware.setMarkedDirty(false);
return true;
} else {
return false;
}
}
return true;
}
}
@@ -3,11 +3,7 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
import io.ebean.annotation.EnumValue;
import io.ebean.annotation.Platform;
import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.JsonConfig;
import io.ebean.config.PlatformConfig;
@@ -34,125 +30,70 @@ import org.slf4j.LoggerFactory;
import javax.persistence.AttributeConverter;
import javax.persistence.EnumType;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.*;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Currency;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.time.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Default implementation of TypeManager.
* <p>
* Manages the list of ScalarType that is available.
* </p>
*/
public final class DefaultTypeManager implements TypeManager {
private static final Logger logger = LoggerFactory.getLogger(DefaultTypeManager.class);
private final ConcurrentHashMap<Class<?>, ScalarType<?>> typeMap;
private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap;
private final ConcurrentHashMap<String, ScalarType<?>> logicalMap;
private final DefaultTypeFactory extraTypeFactory;
private final ScalarType<?> hstoreType = new ScalarTypePostgresHstore();
private final ScalarTypeFile fileType = new ScalarTypeFile();
private final ScalarType<?> charType = new ScalarTypeChar();
private final ScalarType<?> charArrayType = new ScalarTypeCharArray();
private final ScalarType<?> longVarcharType = new ScalarTypeLongVarchar();
private final ScalarType<?> clobType = new ScalarTypeClob();
private final ScalarType<?> byteType = new ScalarTypeByte();
private final ScalarType<?> binaryType = new ScalarTypeBytesBinary();
private final ScalarType<?> blobType = new ScalarTypeBytesBlob();
private final ScalarType<?> varbinaryType = new ScalarTypeBytesVarbinary();
private final ScalarType<?> longVarbinaryType = new ScalarTypeBytesLongVarbinary();
private final ScalarType<?> shortType = new ScalarTypeShort();
private final ScalarType<?> integerType = ScalarTypeInteger.INSTANCE;
private final ScalarType<?> longType = new ScalarTypeLong();
private final ScalarType<?> doubleType = new ScalarTypeDouble();
private final ScalarType<?> floatType = new ScalarTypeFloat();
private final ScalarType<?> bigDecimalType = new ScalarTypeBigDecimal();
private final ScalarType<?> timeType = new ScalarTypeTime();
private final ScalarType<?> urlType = new ScalarTypeURL();
private final ScalarType<?> uriType = new ScalarTypeURI();
private final ScalarType<?> localeType = new ScalarTypeLocale();
private final ScalarType<?> currencyType = new ScalarTypeCurrency();
private final ScalarType<?> timeZoneType = new ScalarTypeTimeZone();
private final ScalarType<?> stringType = ScalarTypeString.INSTANCE;
private final ScalarType<?> classType = new ScalarTypeClass();
private final JsonConfig.DateTime jsonDateTime;
private final JsonConfig.Date jsonDate;
private final Object objectMapper;
private final boolean objectMapperPresent;
private final boolean postgres;
private final TypeJsonManager jsonManager;
private final boolean offlineMigrationGeneration;
private final EnumType defaultEnumType;
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
@@ -191,24 +132,20 @@ public final class DefaultTypeManager implements TypeManager {
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
this.postgres = isPostgres(config.getDatabasePlatform());
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null;
this.extraTypeFactory = new DefaultTypeFactory(config);
this.postgres = isPostgres(config.getDatabasePlatform());
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
this.defaultEnumType = config.getDefaultEnumType();
initialiseStandard(config);
initialiseJavaTimeTypes(config);
initialiseJodaTypes(config);
initialiseJacksonTypes(config);
loadTypesFromProviders(config, objectMapper);
loadGeoTypeBinder(config);
@@ -489,7 +426,7 @@ public final class DefaultTypeManager implements TypeManager {
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType);
return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType);
}
/**
@@ -588,8 +525,7 @@ public final class DefaultTypeManager implements TypeManager {
private ScalarTypeEnum<?> createEnumScalarType2(Class<?> enumType) {
boolean integerType = true;
Map<String, String> nameValueMap = new LinkedHashMap<>();
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
for (Field field : enumType.getDeclaredFields()) {
EnumValue enumValue = AnnotationUtil.get(field, EnumValue.class);
if (enumValue != null) {
nameValueMap.put(field.getName(), enumValue.value());
@@ -652,8 +588,7 @@ public final class DefaultTypeManager implements TypeManager {
}
private ScalarTypeEnum<?> createEnumScalarTypePerExtentions(Class<? extends Enum<?>> enumType) {
Method[] methods = enumType.getMethods();
for (Method method : methods) {
for (Method method : enumType.getMethods()) {
DbEnumValue dbValue = AnnotationUtil.get(method, DbEnumValue.class);
if (dbValue != null) {
boolean integerValues = DbEnumType.INTEGER == dbValue.storage();
@@ -668,7 +603,6 @@ public final class DefaultTypeManager implements TypeManager {
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
* <p>
* Return null if the EnumValue annotations are not present/used.
* </p>
*/
private ScalarTypeEnum<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType, int length, boolean withConstraint) {
Map<String, String> nameValueMap = new LinkedHashMap<>();
@@ -697,11 +631,11 @@ public final class DefaultTypeManager implements TypeManager {
EnumToDbValueMap<?> beanDbMap = EnumToDbValueMap.create(integerType);
int maxValueLen = 0;
for (Map.Entry<String, String> entry : nameValueMap.entrySet()) {
String name = entry.getKey();
String name = entry.getKey().trim();
String value = entry.getValue();
maxValueLen = Math.max(maxValueLen, value.length());
Object enumValue = Enum.valueOf(enumType, name.trim());
beanDbMap.add(enumValue, value, name.trim());
Object enumValue = Enum.valueOf(enumType, name);
beanDbMap.add(enumValue, value, name);
}
if (dbColumnLength == 0 && !integerType) {
dbColumnLength = maxValueLen;
@@ -711,31 +645,24 @@ public final class DefaultTypeManager implements TypeManager {
/**
* Automatically find any ScalarTypes by searching through the class path.
* <p>
* In avaje.properties define a list of packages in which ScalarTypes are
* found. This will search for any class that implements the ScalarType
* interface and register it with this TypeManager.
* </p>
*/
private void initialiseCustomScalarTypes(BootupClasses bootupClasses) {
for (Class<? extends ScalarType<?>> cls : bootupClasses.getScalarTypes()) {
try {
ScalarType<?> scalarType;
if (objectMapper == null) {
scalarType = cls.newInstance();
scalarType = cls.getDeclaredConstructor().newInstance();
} else {
try {
// first try objectMapper constructor
Constructor<? extends ScalarType<?>> constructor = cls.getConstructor(ObjectMapper.class);
scalarType = constructor.newInstance((ObjectMapper) objectMapper);
scalarType = cls.getDeclaredConstructor(ObjectMapper.class).newInstance(objectMapper);
} catch (NoSuchMethodException e) {
scalarType = cls.newInstance();
scalarType = cls.getDeclaredConstructor().newInstance();
}
}
addCustomType(scalarType);
} catch (Exception e) {
String msg = "Error loading ScalarType [" + cls.getName() + "]";
logger.error(msg, e);
logger.error("Error loading ScalarType [" + cls.getName() + "]", e);
}
}
}
@@ -755,8 +682,7 @@ public final class DefaultTypeManager implements TypeManager {
@SuppressWarnings({"unchecked", "rawtypes"})
private void initialiseScalarConverters(BootupClasses bootupClasses) {
List<Class<? extends ScalarTypeConverter<?, ?>>> foundTypes = bootupClasses.getScalarConverters();
for (Class<? extends ScalarTypeConverter<?, ?>> foundType : foundTypes) {
for (Class<? extends ScalarTypeConverter<?, ?>> foundType : bootupClasses.getScalarConverters()) {
try {
Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, ScalarTypeConverter.class);
if (paramTypes.length != 2) {
@@ -768,7 +694,7 @@ public final class DefaultTypeManager implements TypeManager {
if (wrappedType == null) {
throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]);
}
ScalarTypeConverter converter = foundType.newInstance();
ScalarTypeConverter converter = foundType.getDeclaredConstructor().newInstance();
ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter);
logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType);
add(stw);
@@ -780,8 +706,7 @@ public final class DefaultTypeManager implements TypeManager {
@SuppressWarnings({"unchecked", "rawtypes"})
private void initialiseAttributeConverters(BootupClasses bootupClasses) {
List<Class<? extends AttributeConverter<?, ?>>> foundTypes = bootupClasses.getAttributeConverters();
for (Class<? extends AttributeConverter<?, ?>> foundType : foundTypes) {
for (Class<? extends AttributeConverter<?, ?>> foundType : bootupClasses.getAttributeConverters()) {
try {
Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, AttributeConverter.class);
if (paramTypes.length != 2) {
@@ -793,7 +718,7 @@ public final class DefaultTypeManager implements TypeManager {
if (wrappedType == null) {
throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]);
}
AttributeConverter converter = foundType.newInstance();
AttributeConverter converter = foundType.getDeclaredConstructor().newInstance();
ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter));
logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType);
add(stw);
@@ -825,12 +750,15 @@ public final class DefaultTypeManager implements TypeManager {
}
private void initialiseJavaTimeTypes(DatabaseConfig config) {
ZoneId zoneId = getZoneId(config);
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
addType(java.time.Period.class, new ScalarTypePeriod());
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId));
addType(Instant.class, new ScalarTypeInstant(jsonDateTime));
addType(DayOfWeek.class, new ScalarTypeDayOfWeek());
addType(Month.class, new ScalarTypeMonth());
@@ -846,6 +774,11 @@ public final class DefaultTypeManager implements TypeManager {
addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration());
}
private ZoneId getZoneId(DatabaseConfig config) {
final String dataTimeZone = config.getDataTimeZone();
return (dataTimeZone == null) ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(dataTimeZone).toZoneId();
}
private void addType(Class<?> clazz, ScalarType<?> scalarType) {
typeMap.put(clazz, scalarType);
logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType);
@@ -890,23 +823,16 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(DbPlatformType.HSTORE, hstoreType);
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(jsonDateTime, jsonDate);
addType(java.util.Date.class, utilDateType);
ScalarType<?> calType = extraTypeFactory.createCalendar(jsonDateTime);
addType(Calendar.class, calType);
ScalarType<?> mathBigIntType = extraTypeFactory.createMathBigInteger();
addType(BigInteger.class, mathBigIntType);
addType(java.util.Date.class, extraTypeFactory.createUtilDate(jsonDateTime, jsonDate));
addType(Calendar.class, extraTypeFactory.createCalendar(jsonDateTime));
addType(BigInteger.class, extraTypeFactory.createMathBigInteger());
ScalarTypeBool booleanType = extraTypeFactory.createBoolean();
addType(Boolean.class, booleanType);
addType(boolean.class, booleanType);
// register the boolean literals to the platform for DDL default values
databasePlatform.setDbTrueLiteral(booleanType.getDbTrueLiteral());
databasePlatform.setDbFalseLiteral(booleanType.getDbFalseLiteral());
// always register Types.BOOLEAN to our boolean type
nativeMap.put(Types.BOOLEAN, booleanType);
if (booleanType.getJdbcType() == Types.BIT) {
@@ -915,7 +841,6 @@ public final class DefaultTypeManager implements TypeManager {
}
PlatformConfig.DbUuid dbUuid = config.getPlatformConfig().getDbUuid();
if (offlineMigrationGeneration || (databasePlatform.isNativeUuidType() && dbUuid.useNativeType())) {
addType(UUID.class, new ScalarTypeUUIDNative());
} else {
@@ -1027,6 +952,7 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(Types.TIMESTAMP, timestampType);
}
@SuppressWarnings("rawtypes")
private void addInetAddressType(ScalarType scalarType) {
addType(InetAddress.class, scalarType);
addType(Inet4Address.class, scalarType);
Loaded 100 of 246 files, more files were not shown because too many files have changed in this diff. Show more