mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b72924529 | ||
|
|
a75ed676f8 | ||
|
|
c3438b888d | ||
|
|
56be274acc | ||
|
|
84bb4ff78c | ||
|
|
4054eda1f6 | ||
|
|
a4b9354e43 | ||
|
|
06e033f23f | ||
|
|
156f505394 | ||
|
|
9b2b2f7787 | ||
|
|
e68dae0baa | ||
|
|
2cf864954f | ||
|
|
2b94487573 | ||
|
|
fcf730ec39 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.4.1</version>
|
||||
<version>11.5.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.4.1</tag>
|
||||
<tag>ebean-11.5.1</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -92,7 +92,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.2</version>
|
||||
<version>3.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -215,7 +215,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.4.1</version>
|
||||
<version>11.5.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.4.1</version>
|
||||
<version>11.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -54,6 +54,22 @@ public class ClassLoadConfig {
|
||||
return isPresent("javax.validation.constraints.NotNull");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax PostConstruct annotation is present (maybe not in java9).
|
||||
* If not we don't support PostConstruct lifecycle events.
|
||||
*/
|
||||
public boolean isJavaxPostConstructPresent() {
|
||||
return isPresent("javax.annotation.PostConstruct");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax JAXB is present (maybe not in java9).
|
||||
* If not we don't try to parse or support 'extra ddl'.
|
||||
*/
|
||||
public boolean isJavaxJAXBPresent() {
|
||||
return isPresent("javax.xml.bind.JAXBException");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Jackson annotations like JsonIgnore are present.
|
||||
*/
|
||||
|
||||
@@ -435,6 +435,11 @@ public class ServerConfig {
|
||||
|
||||
private String jodaLocalTimeMode;
|
||||
|
||||
/**
|
||||
* Time to live for query plans - defaults to 5 minutes.
|
||||
*/
|
||||
private int queryPlanTTLSeconds = 60 * 5;
|
||||
|
||||
/**
|
||||
* Set to true to globally disable L2 caching (typically for performance testing).
|
||||
*/
|
||||
@@ -2595,6 +2600,7 @@ public class ServerConfig {
|
||||
dbTypeConfig.setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
@@ -2817,6 +2823,20 @@ public class ServerConfig {
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan time to live.
|
||||
*/
|
||||
public int getQueryPlanTTLSeconds() {
|
||||
return queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query plan time to live.
|
||||
*/
|
||||
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
|
||||
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the DB migration against the DataSource.
|
||||
*/
|
||||
|
||||
@@ -89,6 +89,10 @@ public class ScopeTrans {
|
||||
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ScopeTrans[" + transaction + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current/active transaction.
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,10 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ScopedTransaction[" + current + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the scope transaction.
|
||||
*/
|
||||
@@ -56,6 +60,7 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
private void pop() {
|
||||
if (!stack.isEmpty()) {
|
||||
current = stack.pop();
|
||||
transaction = current.getTransaction();
|
||||
} else {
|
||||
manager.set(null);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public class DdlGenerator {
|
||||
private final boolean generateDdl;
|
||||
private final boolean runDdl;
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -43,6 +44,7 @@ public class DdlGenerator {
|
||||
|
||||
public DdlGenerator(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
this.server = server;
|
||||
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
if (serverConfig.getTenantMode().isDynamicDataSource() && serverConfig.isDdlRun()) {
|
||||
@@ -139,7 +141,7 @@ public class DdlGenerator {
|
||||
runScript(false, createAllContent, getCreateFileName());
|
||||
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl)) {
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
|
||||
@@ -724,33 +724,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* This will also potentially throw exceptions for MANDATORY and NEVER types.
|
||||
* </p>
|
||||
*/
|
||||
private boolean createNewTransaction(SpiTransaction t, TxScope scope) {
|
||||
|
||||
TxType type = scope.getType();
|
||||
private boolean createNewTransaction(SpiTransaction current, TxType type) {
|
||||
switch (type) {
|
||||
case REQUIRED:
|
||||
return t == null;
|
||||
return current == null;
|
||||
|
||||
case REQUIRES_NEW:
|
||||
return true;
|
||||
|
||||
case MANDATORY:
|
||||
if (t == null) {
|
||||
if (current == null) {
|
||||
throw new PersistenceException("Transaction missing when MANDATORY");
|
||||
}
|
||||
return false;
|
||||
|
||||
case SUPPORTS:
|
||||
return current == null;
|
||||
|
||||
case NEVER:
|
||||
if (t != null) {
|
||||
if (current != null) {
|
||||
throw new PersistenceException("Transaction exists for Transactional NEVER");
|
||||
}
|
||||
return false;
|
||||
|
||||
case SUPPORTS:
|
||||
return false;
|
||||
return true; // always use NoTransaction instance
|
||||
|
||||
case NOT_SUPPORTED:
|
||||
throw new RuntimeException("NOT_SUPPORTED should already be handled?");
|
||||
return true; // always use NoTransaction instance
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Should never get here?");
|
||||
@@ -811,14 +809,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
SpiTransaction transaction = txnContainer.current();
|
||||
|
||||
boolean createTransaction;
|
||||
if (txScope.getType() == TxType.NOT_SUPPORTED) {
|
||||
createTransaction = false;
|
||||
transaction = null;
|
||||
} else {
|
||||
createTransaction = createNewTransaction(transaction, txScope);
|
||||
if (createTransaction) {
|
||||
transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel());
|
||||
TxType type = txScope.getType();
|
||||
boolean createTransaction = createNewTransaction(transaction, type);
|
||||
if (createTransaction) {
|
||||
switch (type) {
|
||||
case SUPPORTS:
|
||||
case NOT_SUPPORTED:
|
||||
case NEVER:
|
||||
transaction = NoTransaction.INSTANCE;
|
||||
break;
|
||||
default:
|
||||
transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ public class InternalConfiguration {
|
||||
this.beanDescriptorManager = new BeanDescriptorManager(this);
|
||||
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
|
||||
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
|
||||
beanDescriptorManager.scheduleBackgroundTrim();
|
||||
|
||||
this.dataTimeZone = initDataTimeZone();
|
||||
this.binder = getBinder(typeManager, databasePlatform, dataTimeZone);
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
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.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.transaction.ProfileStream;
|
||||
import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Place holder for use with SUPPORTS and NEVER transactional when there really isn't a transaction.
|
||||
*/
|
||||
class NoTransaction implements SpiTransaction {
|
||||
|
||||
static final NoTransaction INSTANCE = new NoTransaction();
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
// always false
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitAndContinue() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws PersistenceException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void rollback(Throwable e) throws PersistenceException {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSql() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSummary() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSql(String msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSummary(String msg) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeferred(PersistDeferredRelationship derived) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleteBean(Integer hash) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDeleteBean(Integer hash) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisteredDeleteBean(Integer hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterBean(Object bean) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisteredBean(Object bean) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isUpdateAllLoadedProperties() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreMode getDocStoreMode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocStoreBatchSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TransactionCallback callback) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setDocStoreMode(DocStoreMode mode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocStoreBatchSize(int batchSize) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistCascade(boolean persistCascade) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkipCache(boolean skipCache) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipCache() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchMode(boolean useBatch) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch persistBatchMode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchSize(int batchSize) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchFlushOnQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws PersistenceException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatch() throws PersistenceException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putUserObject(String name, Object value) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUserObject(String name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBatchGetGeneratedKeys() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depth(int diff) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int depth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicit() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionEvent getEvent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistCascade() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest(PersistRequest.Type type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchControl getBatchControl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchControl(BatchControl control) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistenceContext(PersistenceContext context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getInternalConnection() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnCascade() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnRollback() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException translate(String message, SQLException cause) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markNotQueryOnly() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkBatchEscalationOnCollection() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnCollection() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChangeLog(ChangeSet changeSet) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreTransaction getDocStoreTransaction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTenantId(Object tenantId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTenantId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long profileOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profileEvent(SpiProfileTransactionEvent event) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileStream profileStream() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,7 @@ import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -1530,6 +1531,24 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim query plans not used since the passed in epoch time.
|
||||
*/
|
||||
public List<CQueryPlan> trimQueryPlans(long unusedSince) {
|
||||
|
||||
List<CQueryPlan> list = new ArrayList<>();
|
||||
|
||||
Iterator<CQueryPlan> it = queryPlanCache.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CQueryPlan queryPlan = it.next();
|
||||
if (queryPlan.getLastQueryTime() < unusedSince) {
|
||||
it.remove();
|
||||
list.add(queryPlan);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the postLoad if a BeanPostLoad exists for this bean.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
import io.ebeaninternal.server.properties.BeanPropertiesReader;
|
||||
import io.ebeaninternal.server.properties.BeanPropertyAccess;
|
||||
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.xmlmapping.XmlMappingReader;
|
||||
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
|
||||
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
|
||||
@@ -80,6 +81,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -190,6 +192,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
*/
|
||||
private final Map<String, String> draftTableMap = new HashMap<>();
|
||||
|
||||
private final int queryPlanTTLSeconds;
|
||||
|
||||
/**
|
||||
* Create for a given database dbConfig.
|
||||
*/
|
||||
@@ -207,6 +211,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
this.multiValueBind = config.getMultiValueBind();
|
||||
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind);
|
||||
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
|
||||
this.queryPlanTTLSeconds = serverConfig.getQueryPlanTTLSeconds();
|
||||
|
||||
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
|
||||
String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig);
|
||||
@@ -222,7 +227,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
this.updateChangesOnly = serverConfig.isUpdateChangesOnly();
|
||||
|
||||
this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory();
|
||||
this.beanLifecycleAdapterFactory = new BeanLifecycleAdapterFactory(serverConfig);
|
||||
this.persistControllerManager = new PersistControllerManager(bootupClasses);
|
||||
this.postLoadManager = new PostLoadManager(bootupClasses);
|
||||
this.postConstructManager = new PostConstructManager(bootupClasses);
|
||||
@@ -236,6 +241,25 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
this.changeLogRegister = config.changeLogRegister(bootupClasses.getChangeLogRegister());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run periodic trim of query plans.
|
||||
*/
|
||||
public void scheduleBackgroundTrim() {
|
||||
backgroundExecutor.executePeriodically(this::trimQueryPlans, 30L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void trimQueryPlans() {
|
||||
long lastUsed = System.currentTimeMillis() - (queryPlanTTLSeconds * 1000L);
|
||||
for (BeanDescriptor<?> descriptor : immutableDescriptorList) {
|
||||
if (!descriptor.isEmbedded()) {
|
||||
List<CQueryPlan> trimmedPlans = descriptor.trimQueryPlans(lastUsed);
|
||||
if (!trimmedPlans.isEmpty()) {
|
||||
logger.trace("trimmed {} query plans for type:{}", trimmedPlans.size(), descriptor.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the AsOfViewSuffix based on the DbHistorySupport.
|
||||
*/
|
||||
|
||||
@@ -41,8 +41,10 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getDeployOrder() {
|
||||
return deployOrder;
|
||||
@Override
|
||||
public int getFetchPreference() {
|
||||
// return some decently high value
|
||||
return 1000;
|
||||
}
|
||||
|
||||
private String calcPlaceHolder(String prefix, String dbColumn) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.annotation.PostSoftDelete;
|
||||
import io.ebean.annotation.PreSoftDelete;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.event.BeanPersistAdapter;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
import io.ebean.event.BeanPostConstructListener;
|
||||
@@ -31,6 +32,12 @@ import java.util.List;
|
||||
*/
|
||||
class BeanLifecycleAdapterFactory {
|
||||
|
||||
private final boolean postConstructPresent;
|
||||
|
||||
BeanLifecycleAdapterFactory(ServerConfig serverConfig) {
|
||||
this.postConstructPresent = serverConfig.getClassLoadConfig().isJavaxPostConstructPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a BeanPersistController for methods annotated with lifecycle events.
|
||||
*/
|
||||
@@ -41,7 +48,7 @@ class BeanLifecycleAdapterFactory {
|
||||
// look for annotated methods
|
||||
MethodsHolder methodHolder = new MethodsHolder();
|
||||
for (Method m : methods) {
|
||||
methodHolder.checkMethod(m);
|
||||
methodHolder.checkMethod(m, postConstructPresent);
|
||||
}
|
||||
|
||||
if (methodHolder.hasPersistMethods()) {
|
||||
@@ -86,7 +93,7 @@ class BeanLifecycleAdapterFactory {
|
||||
/**
|
||||
* Check the method for all the annotations we are interested in.
|
||||
*/
|
||||
private void checkMethod(Method method) {
|
||||
private void checkMethod(Method method, boolean postConstructPresent) {
|
||||
if (method.isAnnotationPresent(PrePersist.class)) {
|
||||
preInserts.add(method);
|
||||
hasPersistMethods = true;
|
||||
@@ -125,8 +132,10 @@ class BeanLifecycleAdapterFactory {
|
||||
if (method.isAnnotationPresent(PostLoad.class)) {
|
||||
postLoads.add(method);
|
||||
}
|
||||
if (method.isAnnotationPresent(PostConstruct.class)) {
|
||||
postConstructs.add(method);
|
||||
if (postConstructPresent) {
|
||||
if (method.isAnnotationPresent(PostConstruct.class)) {
|
||||
postConstructs.add(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,6 +537,12 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFetchPreference() {
|
||||
// return some decently high value - override on ToMany property
|
||||
return 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any extra joins required to support this property. Generally a no
|
||||
* operation except for a OneToOne exported.
|
||||
|
||||
@@ -73,6 +73,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
|
||||
final String extraWhere;
|
||||
|
||||
final int fetchPreference;
|
||||
|
||||
boolean saveRecurseSkippable;
|
||||
|
||||
/**
|
||||
@@ -88,6 +90,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
|
||||
this.targetType = deploy.getTargetType();
|
||||
this.cascadeInfo = deploy.getCascadeInfo();
|
||||
this.fetchPreference = deploy.getFetchPreference();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,6 +112,11 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFetchPreference() {
|
||||
return fetchPreference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ElPropertyValue for a *ToOne or *ToMany.
|
||||
*/
|
||||
|
||||
@@ -297,7 +297,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
/**
|
||||
* Find the Id's of detail beans given a parent Id or list of parent Id's.
|
||||
*/
|
||||
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, ArrayList<Object> excludeDetailIds) {
|
||||
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
|
||||
if (parentId != null) {
|
||||
return findIdsByParentId(parentId, t, excludeDetailIds);
|
||||
} else {
|
||||
@@ -305,7 +305,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> findIdsByParentId(Object parentId, Transaction t, ArrayList<Object> excludeDetailIds) {
|
||||
private List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(false, "");
|
||||
List<Object> bindValues = new ArrayList<>();
|
||||
@@ -374,7 +374,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t, ArrayList<Object> excludeDetailIds) {
|
||||
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(true, "");
|
||||
String inClause = buildInClauseBinding(parentIdList.size(), exportedPropertyBindProto);
|
||||
@@ -402,19 +402,19 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
private SqlUpdate deleteByParentIdList(List<Object> parentIdist) {
|
||||
private SqlUpdate deleteByParentIdList(List<Object> parentIdList) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append(deleteByParentIdInSql);
|
||||
|
||||
String inClause = buildInClauseBinding(parentIdist.size(), exportedPropertyBindProto);
|
||||
String inClause = buildInClauseBinding(parentIdList.size(), exportedPropertyBindProto);
|
||||
sb.append(inClause);
|
||||
|
||||
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
|
||||
if (exportedProperties.length == 1) {
|
||||
bindWhereParendId(delete, new MultiValueWrapper(parentIdist));
|
||||
bindWhereParendId(delete, new MultiValueWrapper(parentIdList));
|
||||
} else {
|
||||
for (Object aParentIdist : parentIdist) {
|
||||
for (Object aParentIdist : parentIdList) {
|
||||
bindWhereParendId(delete, aParentIdist);
|
||||
}
|
||||
}
|
||||
@@ -900,7 +900,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
throw new PersistenceException(from + ": Could not find mapKey property [" + mapKey + "] on [" + to + "]");
|
||||
}
|
||||
|
||||
public IntersectionRow buildManyDeleteChildren(EntityBean parentBean, ArrayList<Object> excludeDetailIds) {
|
||||
public IntersectionRow buildManyDeleteChildren(EntityBean parentBean, List<Object> excludeDetailIds) {
|
||||
|
||||
IntersectionRow row = new IntersectionRow(tableJoin.getTable(), targetDescriptor);
|
||||
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebeaninternal.server.core.DefaultSqlUpdate;
|
||||
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
|
||||
import io.ebeaninternal.server.expression.IdInExpression;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -20,7 +19,7 @@ public class IntersectionRow {
|
||||
|
||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<>();
|
||||
|
||||
private ArrayList<Object> excludeIds;
|
||||
private List<Object> excludeIds;
|
||||
private BeanDescriptor<?> excludeDescriptor;
|
||||
|
||||
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
|
||||
@@ -36,7 +35,7 @@ public class IntersectionRow {
|
||||
/**
|
||||
* Set Id's to exclude. This is for deleting non-attached detail Id's.
|
||||
*/
|
||||
public void setExcludeIds(ArrayList<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
|
||||
public void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
|
||||
this.excludeIds = excludeIds;
|
||||
this.excludeDescriptor = excludeDescriptor;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
|
||||
|
||||
private String docStoreDoc;
|
||||
|
||||
private int fetchPreference = 1000;
|
||||
|
||||
/**
|
||||
* Construct the property.
|
||||
*/
|
||||
@@ -147,4 +149,11 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
|
||||
return docStoreDoc;
|
||||
}
|
||||
|
||||
public int getFetchPreference() {
|
||||
return fetchPreference;
|
||||
}
|
||||
|
||||
public void setFetchPreference(int fetchPreference) {
|
||||
this.fetchPreference = fetchPreference;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import io.ebean.annotation.FetchPreference;
|
||||
import io.ebean.annotation.HistoryExclude;
|
||||
import io.ebean.annotation.PrivateOwned;
|
||||
import io.ebean.annotation.Where;
|
||||
@@ -90,6 +91,11 @@ class AnnotationAssocManys extends AnnotationParser {
|
||||
prop.setExtraWhere(where.clause());
|
||||
}
|
||||
|
||||
FetchPreference fetchPreference = get(prop, FetchPreference.class);
|
||||
if (fetchPreference != null) {
|
||||
prop.setFetchPreference(fetchPreference.value());
|
||||
}
|
||||
|
||||
// check for manually defined joins
|
||||
BeanTable beanTable = prop.getBeanTable();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import io.ebean.annotation.FetchPreference;
|
||||
import io.ebean.annotation.Where;
|
||||
import io.ebean.config.NamingConvention;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
@@ -87,6 +88,11 @@ public class AnnotationAssocOnes extends AnnotationParser {
|
||||
prop.setExtraWhere(where.clause());
|
||||
}
|
||||
|
||||
FetchPreference fetchPreference = get(prop, FetchPreference.class);
|
||||
if (fetchPreference != null) {
|
||||
prop.setFetchPreference(fetchPreference.value());
|
||||
}
|
||||
|
||||
if (validationAnnotations) {
|
||||
NotNull notNull = get(prop, NotNull.class);
|
||||
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
|
||||
|
||||
@@ -84,6 +84,11 @@ public class ElPropertyChain implements ElPropertyValue {
|
||||
return "expr:" + expression + " chain:" + Arrays.toString(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFetchPreference() {
|
||||
return chain[0].getFetchPreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregation() {
|
||||
return false;
|
||||
|
||||
@@ -74,4 +74,10 @@ public interface ElPropertyDeploy {
|
||||
* Return true if this is an aggregation property.
|
||||
*/
|
||||
boolean isAggregation();
|
||||
|
||||
/**
|
||||
* Return the fetch preference. This can be used to control which ToMany relationship
|
||||
* is left as a 'join' and which get converted to query join.
|
||||
*/
|
||||
int getFetchPreference();
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
class RawExpression extends NonPrepareExpression {
|
||||
|
||||
@@ -43,7 +45,12 @@ class RawExpression extends NonPrepareExpression {
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
if (values != null) {
|
||||
for (Object value : values) {
|
||||
request.addBindValue(value);
|
||||
if (value instanceof Collection<?>) {
|
||||
// support for Postgres = any(?) type raw expression
|
||||
request.addBindValue(new MultiValueWrapper((Collection<?>)value));
|
||||
} else {
|
||||
request.addBindValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,20 +949,15 @@ public final class DefaultPersister implements Persister {
|
||||
Set<?> modifyRemovals = c.getModifyRemovals();
|
||||
saveMany.modifyListenReset(c);
|
||||
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
|
||||
|
||||
SpiTransaction t = saveMany.getTransaction();
|
||||
// increase depth for batching order
|
||||
t.depth(+1);
|
||||
for (Object removedBean : modifyRemovals) {
|
||||
if (removedBean instanceof EntityBean) {
|
||||
EntityBean eb = (EntityBean) removedBean;
|
||||
if (eb._ebean_getIntercept().isLoaded()) {
|
||||
// only delete if the bean was loaded meaning that it is known to exist in the DB
|
||||
deleteRequest(createPublishRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
|
||||
deleteRequest(createPublishRequest(removedBean, saveMany.getTransaction(), PersistRequest.Type.DELETE, saveMany.isPublish()));
|
||||
}
|
||||
}
|
||||
}
|
||||
t.depth(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -992,21 +987,23 @@ public final class DefaultPersister implements Persister {
|
||||
targetDescriptor.preAllocateIds(collection.size());
|
||||
}
|
||||
|
||||
ArrayList<Object> detailIds = null;
|
||||
SpiTransaction t = saveMany.getTransaction();
|
||||
boolean isMap = ManyType.MAP.equals(prop.getManyType());
|
||||
EntityBean parentBean = saveMany.getParentBean();
|
||||
|
||||
if (deleteMissingChildren) {
|
||||
// collect the Id's (to exclude from deleteManyDetails)
|
||||
detailIds = new ArrayList<>();
|
||||
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
|
||||
// deleting missing children - children not in our collected detailIds
|
||||
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds, false);
|
||||
}
|
||||
|
||||
// increase depth for batching order
|
||||
SpiTransaction t = saveMany.getTransaction();
|
||||
t.depth(+1);
|
||||
|
||||
// if a map, then we get the key value and
|
||||
// set it to the appropriate property on the
|
||||
// detail bean before we save it
|
||||
boolean isMap = ManyType.MAP.equals(prop.getManyType());
|
||||
EntityBean parentBean = saveMany.getParentBean();
|
||||
Object mapKeyValue = null;
|
||||
|
||||
boolean saveSkippable = prop.isSaveRecurseSkippable();
|
||||
@@ -1047,28 +1044,30 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
|
||||
if (detailIds != null) {
|
||||
// stateless update with deleteMissingChildren so first
|
||||
// flushBatch to ensure that we have Id values from any inserts
|
||||
t.flushBatch();
|
||||
// now collect the Id values to determine the 'missing children'
|
||||
for (Object detailBean : collection) {
|
||||
if (isMap) {
|
||||
detailBean = ((Map.Entry<?, ?>) detailBean).getValue();
|
||||
}
|
||||
if (detailBean instanceof EntityBean) {
|
||||
Object id = targetDescriptor.getId((EntityBean) detailBean);
|
||||
if (!DmlUtil.isNullOrZero(id)) {
|
||||
// remember the Id (other details not in the collection) will be removed
|
||||
detailIds.add(id);
|
||||
}
|
||||
t.depth(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the Id values of the details to remove 'missing children' for stateless updates.
|
||||
*/
|
||||
private List<Object> collectIds(Collection<?> collection, BeanDescriptor<?> targetDescriptor, boolean isMap) {
|
||||
|
||||
List<Object> detailIds = new ArrayList<>();
|
||||
// stateless update with deleteMissingChildren so first
|
||||
// collect the Id values to remove the 'missing children'
|
||||
for (Object detailBean : collection) {
|
||||
if (isMap) {
|
||||
detailBean = ((Map.Entry<?, ?>) detailBean).getValue();
|
||||
}
|
||||
if (detailBean instanceof EntityBean) {
|
||||
Object id = targetDescriptor.getId((EntityBean) detailBean);
|
||||
if (!DmlUtil.isNullOrZero(id)) {
|
||||
// remember the Id (other details not in the collection) will be removed
|
||||
detailIds.add(id);
|
||||
}
|
||||
}
|
||||
// deleting missing children - children not in our collected detailIds
|
||||
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds, false);
|
||||
}
|
||||
|
||||
t.depth(-1);
|
||||
return detailIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1268,7 +1267,7 @@ public final class DefaultPersister implements Persister {
|
||||
* </p>
|
||||
*/
|
||||
private void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
|
||||
BeanPropertyAssocMany<?> many, ArrayList<Object> excludeDetailIds, boolean softDelete) {
|
||||
BeanPropertyAssocMany<?> many, List<Object> excludeDetailIds, boolean softDelete) {
|
||||
|
||||
if (many.getCascadeInfo().isDelete()) {
|
||||
// cascade delete the beans in the collection
|
||||
|
||||
@@ -252,7 +252,7 @@ class CQueryBuilder {
|
||||
// skip building the SqlTree and Sql string
|
||||
predicates.prepare(false);
|
||||
String sql = queryPlan.getSql();
|
||||
return new CQueryRowCount(request, predicates, sql);
|
||||
return new CQueryRowCount(queryPlan, request, predicates, sql);
|
||||
}
|
||||
|
||||
predicates.prepare(true);
|
||||
@@ -283,7 +283,7 @@ class CQueryBuilder {
|
||||
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
|
||||
request.putQueryPlan(queryPlan);
|
||||
|
||||
return new CQueryRowCount(request, predicates, sql);
|
||||
return new CQueryRowCount(queryPlan, request, predicates, sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,8 @@ import java.sql.SQLException;
|
||||
*/
|
||||
class CQueryRowCount {
|
||||
|
||||
private final CQueryPlan queryPlan;
|
||||
|
||||
/**
|
||||
* The overall find request wrapper object.
|
||||
*/
|
||||
@@ -54,7 +56,8 @@ class CQueryRowCount {
|
||||
/**
|
||||
* Create the Sql select based on the request.
|
||||
*/
|
||||
CQueryRowCount(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
|
||||
CQueryRowCount(CQueryPlan queryPlan, OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
|
||||
this.queryPlan = queryPlan;
|
||||
this.request = request;
|
||||
this.query = request.getQuery();
|
||||
this.sql = sql;
|
||||
@@ -116,6 +119,7 @@ class CQueryRowCount {
|
||||
rowCount = rset.getInt(1);
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode());
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
return rowCount;
|
||||
|
||||
|
||||
@@ -318,6 +318,10 @@ public class OrmQueryDetail implements Serializable {
|
||||
*/
|
||||
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;
|
||||
|
||||
@@ -325,22 +329,19 @@ public class OrmQueryDetail implements Serializable {
|
||||
boolean fetchJoinFirstMany = allowOne;
|
||||
|
||||
sortFetchPaths(beanDescriptor, addIds);
|
||||
List<FetchEntry> pairs = sortByFetchPreference(beanDescriptor);
|
||||
|
||||
for (String fetchPath : fetchPaths.keySet()) {
|
||||
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(fetchPath);
|
||||
if (elProp == null) {
|
||||
throw new PersistenceException("Invalid fetch path " + fetchPath + " from " + beanDescriptor.getFullName());
|
||||
}
|
||||
for (FetchEntry pair : pairs) {
|
||||
ElPropertyDeploy elProp = pair.getElProp();
|
||||
if (elProp.containsManySince(manyFetchProperty)) {
|
||||
|
||||
// this is a join to a *ToMany
|
||||
OrmQueryProperties chunk = fetchPaths.get(fetchPath);
|
||||
OrmQueryProperties chunk = pair.getProperties();
|
||||
if (isQueryJoinCandidate(lazyLoadManyPath, chunk)) {
|
||||
// this is a 'fetch join' (included in main query)
|
||||
if (fetchJoinFirstMany) {
|
||||
// letting the first one remain a 'fetch join'
|
||||
fetchJoinFirstMany = false;
|
||||
manyFetchProperty = fetchPath;
|
||||
manyFetchProperty = pair.getPath();
|
||||
} else {
|
||||
// convert this one over to a 'query join'
|
||||
chunk.markForQueryJoin();
|
||||
@@ -350,6 +351,25 @@ 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()) {
|
||||
String fetchPath = entry.getKey();
|
||||
ElPropertyDeploy elProp = desc.getElPropertyDeploy(fetchPath);
|
||||
if (elProp == null) {
|
||||
throw new PersistenceException("Invalid fetch path " + fetchPath + " from " + desc.getFullName());
|
||||
}
|
||||
entries.add(new FetchEntry(idx++, fetchPath, elProp, entry.getValue()));
|
||||
}
|
||||
Collections.sort(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this path is a candidate for converting to a query join.
|
||||
*/
|
||||
@@ -483,4 +503,45 @@ public class OrmQueryDetail implements Serializable {
|
||||
public Set<Map.Entry<String, OrmQueryProperties>> entries() {
|
||||
return fetchPaths.entrySet();
|
||||
}
|
||||
|
||||
private static class FetchEntry implements Comparable<FetchEntry> {
|
||||
|
||||
private final int index;
|
||||
private final String path;
|
||||
private final OrmQueryProperties properties;
|
||||
private final ElPropertyDeploy elProp;
|
||||
|
||||
FetchEntry(int index, String path, ElPropertyDeploy elProp, OrmQueryProperties value) {
|
||||
this.index = index;
|
||||
this.path = path;
|
||||
this.elProp = elProp;
|
||||
this.properties = value;
|
||||
}
|
||||
|
||||
String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
OrmQueryProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
ElPropertyDeploy getElProp() {
|
||||
return elProp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort by fetchPreference and then by index order.
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(FetchEntry other) {
|
||||
int fp = elProp.getFetchPreference();
|
||||
int op = other.elProp.getFetchPreference();
|
||||
if (fp == op) {
|
||||
return Integer.compare(index, other.index);
|
||||
} else {
|
||||
return (fp < op) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ public class TransactionMap {
|
||||
*/
|
||||
public State getStateWithCreate(String serverName) {
|
||||
|
||||
State state = map.computeIfAbsent(serverName, k -> new State());
|
||||
return state;
|
||||
return map.computeIfAbsent(serverName, k -> new State());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,6 +65,9 @@ public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
|
||||
}
|
||||
|
||||
private byte[] encrypt(T value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String formatValue = wrapped.formatValue(value);
|
||||
return dataEncryptSupport.encryptObject(formatValue);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,11 @@ import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.SqlQuery;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.Update;
|
||||
import io.ebean.annotation.ForPlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.EBasicEncrypt;
|
||||
|
||||
@@ -18,6 +16,7 @@ import java.sql.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestEncrypt extends BaseTestCase {
|
||||
|
||||
@@ -40,8 +39,7 @@ public class TestEncrypt extends BaseTestCase {
|
||||
@ForPlatform(Platform.H2)
|
||||
public void test() {
|
||||
|
||||
Update<EBasicEncrypt> deleteAll = Ebean.createUpdate(EBasicEncrypt.class, "delete from EBasicEncrypt");
|
||||
deleteAll.execute();
|
||||
Ebean.find(EBasicEncrypt.class).delete();
|
||||
|
||||
EBasicEncrypt e = new EBasicEncrypt();
|
||||
e.setName("testname");
|
||||
@@ -63,12 +61,14 @@ public class TestEncrypt extends BaseTestCase {
|
||||
|
||||
e1.setName("testmod");
|
||||
e1.setDescription("moddesc");
|
||||
e1.setStatus(EBasicEncrypt.Status.ONE);
|
||||
|
||||
Ebean.save(e1);
|
||||
|
||||
EBasicEncrypt e2 = Ebean.find(EBasicEncrypt.class, e.getId());
|
||||
|
||||
e2.getDescription();
|
||||
assertEquals("moddesc", e2.getDescription());
|
||||
assertEquals(EBasicEncrypt.Status.ONE, e2.getStatus());
|
||||
|
||||
SpiEbeanServer server = (SpiEbeanServer) Ebean.getServer(null);
|
||||
DbEncrypt dbEncrypt = server.getDatabasePlatform().getDbEncrypt();
|
||||
@@ -82,11 +82,11 @@ public class TestEncrypt extends BaseTestCase {
|
||||
List<EBasicEncrypt> list = Ebean.find(EBasicEncrypt.class).where()
|
||||
.eq("description", "moddesc").findList();
|
||||
|
||||
Assert.assertEquals(1, list.size());
|
||||
assertEquals(1, list.size());
|
||||
|
||||
list = Ebean.find(EBasicEncrypt.class).where().startsWith("description", "modde").findList();
|
||||
|
||||
Assert.assertEquals(1, list.size());
|
||||
assertEquals(1, list.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.TSDetail;
|
||||
import org.tests.model.basic.TSMaster;
|
||||
|
||||
public class TestPrivateOwnedCascadeOrder extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
// setup
|
||||
TSMaster master = new TSMaster();
|
||||
master.getDetails().add(new TSDetail("c97", "ONE"));
|
||||
master.getDetails().add(new TSDetail("c96", "TWO"));
|
||||
|
||||
Ebean.save(master);
|
||||
|
||||
// act
|
||||
TSMaster master1 = Ebean.find(master.getClass(), master.getId());
|
||||
|
||||
// Check Ebean deletes the existing c97 first as the unique values clash
|
||||
master1.getDetails().clear();
|
||||
master1.getDetails().add(new TSDetail("c98", "TWO"));
|
||||
|
||||
Ebean.save(master1);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package org.tests.model.basic;
|
||||
import io.ebean.annotation.Encrypted;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.sql.Date;
|
||||
@@ -12,6 +14,11 @@ import java.sql.Timestamp;
|
||||
@Table(name = "e_basicenc")
|
||||
public class EBasicEncrypt {
|
||||
|
||||
public enum Status {
|
||||
ONE,
|
||||
TWO
|
||||
}
|
||||
|
||||
@Id
|
||||
Integer id;
|
||||
|
||||
@@ -24,6 +31,10 @@ public class EBasicEncrypt {
|
||||
@Encrypted(dbLength = 20)
|
||||
Date dob;
|
||||
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
@Encrypted(dbLength = 20)
|
||||
Status status;
|
||||
|
||||
//@Version
|
||||
Timestamp lastUpdate;
|
||||
|
||||
@@ -51,6 +62,14 @@ public class EBasicEncrypt {
|
||||
this.dob = dob;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.tests.model.basic;
|
||||
|
||||
import io.ebean.annotation.Index;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
@@ -23,6 +25,9 @@ public class TSDetail {
|
||||
|
||||
String description;
|
||||
|
||||
@Index(unique = true)
|
||||
String someUniqueValue;
|
||||
|
||||
boolean active;
|
||||
|
||||
@ManyToOne
|
||||
@@ -32,6 +37,11 @@ public class TSDetail {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public TSDetail(String name, String someUniqueValue) {
|
||||
this.name = name;
|
||||
this.someUniqueValue = someUniqueValue;
|
||||
}
|
||||
|
||||
public TSDetail() {
|
||||
|
||||
}
|
||||
@@ -60,6 +70,14 @@ public class TSDetail {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getSomeUniqueValue() {
|
||||
return someUniqueValue;
|
||||
}
|
||||
|
||||
public void setSomeUniqueValue(String someUniqueValue) {
|
||||
this.someUniqueValue = someUniqueValue;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.tests.model.converstation;
|
||||
|
||||
import io.ebean.annotation.FetchPreference;
|
||||
import org.tests.model.BaseModel;
|
||||
|
||||
import javax.persistence.Column;
|
||||
@@ -21,9 +22,11 @@ public class Conversation extends BaseModel {
|
||||
@ManyToOne
|
||||
Group group;
|
||||
|
||||
@FetchPreference(1)
|
||||
@OneToMany(mappedBy = "conversation")
|
||||
List<Participation> participants;
|
||||
|
||||
@FetchPreference(2)
|
||||
@OneToMany(mappedBy = "conversation")
|
||||
List<Message> messages;
|
||||
|
||||
|
||||
@@ -4,12 +4,17 @@ import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Expr;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.ForPlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.tests.model.basic.OrderDetail;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -45,6 +50,25 @@ public class TestWhereRawClause extends BaseTestCase {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@ForPlatform(Platform.POSTGRES)
|
||||
public void testRawPostgresArray() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("Rob");
|
||||
names.add("Fiona");
|
||||
|
||||
List<Customer> list = Ebean.find(Customer.class)
|
||||
.where()
|
||||
.raw("name = any(?)", names)
|
||||
.findList();
|
||||
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawWithBindParams() {
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.tests.query.other;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Query;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.converstation.Conversation;
|
||||
|
||||
import static org.assertj.core.api.StrictAssertions.assertThat;
|
||||
|
||||
public class TestFetchPreference extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void fetchPreference_overrideOrder() {
|
||||
|
||||
Query<Conversation> query = Ebean.find(Conversation.class)
|
||||
// FetchPreference overrides so participants is joined and messages query joined
|
||||
.fetch("messages")
|
||||
.fetch("participants");
|
||||
|
||||
query.findList();
|
||||
String sql = sqlOf(query, 1);
|
||||
assertThat(sql).contains(" from c_conversation t0 left join c_participation t1 ");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void fetchPreference_sr() {
|
||||
|
||||
Query<Conversation> query = Ebean.find(Conversation.class)
|
||||
// without FetchPreference this ToMany would be our first ToMany join
|
||||
// and participants would be query joined
|
||||
.fetch("group.users")
|
||||
.fetch("messages")
|
||||
.fetch("participants");
|
||||
|
||||
query.findList();
|
||||
String sql = sqlOf(query, 1);
|
||||
|
||||
// join to group (the ToOne part only) and participants (our preferred ToMany path)
|
||||
assertThat(sql).contains(" from c_conversation t0 left join c_group t1 on t1.id = t0.group_id left join c_participation t2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchPreference_inOrder() {
|
||||
|
||||
Query<Conversation> query = Ebean.find(Conversation.class)
|
||||
.fetch("participants")
|
||||
.fetch("messages");
|
||||
|
||||
query.findList();
|
||||
String sql = sqlOf(query, 1);
|
||||
assertThat(sql).contains(" from c_conversation t0 left join c_participation t1 ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fetchQuery_onlyOneJoinCandidate() {
|
||||
|
||||
Query<Conversation> query = Ebean.find(Conversation.class)
|
||||
.fetch("messages")
|
||||
.fetchQuery("participants");
|
||||
|
||||
query.findList();
|
||||
String sql = sqlOf(query, 1);
|
||||
|
||||
// participants is explicitly a "query join" so we can join to message
|
||||
assertThat(sql).contains(" from c_conversation t0 left join c_message t1 ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.tests.transaction;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import io.ebean.annotation.TxType;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
public class TestTransactionalNever extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestTransactionalNever.class);
|
||||
|
||||
private Transaction outerTxn;
|
||||
|
||||
@Test
|
||||
public void testWithNever() {
|
||||
|
||||
new SomeTransactionalWithNever().doStuff();
|
||||
}
|
||||
|
||||
@Test(expected = PersistenceException.class)
|
||||
public void testBarfOnExisting() {
|
||||
doWithTransaction();
|
||||
}
|
||||
|
||||
class SomeTransactionalWithNever {
|
||||
|
||||
@Transactional(type = TxType.NEVER)
|
||||
void doStuff() {
|
||||
outerTxn = Ebean.currentTransaction();
|
||||
log.info("currentTransaction ...{}", outerTxn);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
private void doWithTransaction() {
|
||||
|
||||
// always barf
|
||||
new SomeTransactionalWithNever().doStuff();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.tests.transaction;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import io.ebean.annotation.TxType;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class TestTransactionalNotSupports extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestTransactionalNotSupports.class);
|
||||
|
||||
private Transaction outerTxn;
|
||||
|
||||
private Transaction currentTxn;
|
||||
|
||||
@Test
|
||||
public void noCurrentTransaction() {
|
||||
|
||||
outerTxn = null;
|
||||
new SomeTransactionalWithNotSupported().doStuff();
|
||||
assertNull(outerTxn);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void withOuterTransaction_expect_currentTransaction_null_and_originalTxnRestored() {
|
||||
|
||||
outerTxn = null;
|
||||
Ebean.beginTransaction();
|
||||
try {
|
||||
currentTxn = Ebean.currentTransaction();
|
||||
assertNotNull(currentTxn);
|
||||
new SomeTransactionalWithNotSupported().doStuff();
|
||||
|
||||
// there was no current transaction inside the NOT_SUPPORTED
|
||||
assertNull(outerTxn);
|
||||
|
||||
// the original transaction was restored
|
||||
Transaction restored = Ebean.currentTransaction();
|
||||
assertSame(currentTxn, restored);
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
class SomeTransactionalWithNotSupported {
|
||||
|
||||
@Transactional(type = TxType.NOT_SUPPORTED)
|
||||
void doStuff() {
|
||||
outerTxn = Ebean.currentTransaction();
|
||||
log.info("outer ...{}", outerTxn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.tests.transaction;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import io.ebean.annotation.TxType;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class TestTransactionalRequired extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestTransactionalRequired.class);
|
||||
|
||||
private Transaction outerTxn;
|
||||
private Transaction innerTxn;
|
||||
private Transaction currentTxn;
|
||||
|
||||
@Test
|
||||
public void noCurrentTransaction() {
|
||||
|
||||
outerTxn = null;
|
||||
|
||||
assertNull(Ebean.currentTransaction());
|
||||
|
||||
new OuterTransactionalWithRequired().doOuter();
|
||||
|
||||
assertNull(Ebean.currentTransaction());
|
||||
|
||||
assertNotNull(outerTxn);
|
||||
assertNotNull(innerTxn);
|
||||
assertSame(outerTxn, innerTxn);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void withOuterBegin() {
|
||||
|
||||
outerTxn = null;
|
||||
Ebean.beginTransaction();
|
||||
try {
|
||||
currentTxn = Ebean.currentTransaction();
|
||||
assertNotNull(currentTxn);
|
||||
new OuterTransactionalWithRequired().doOuter();
|
||||
|
||||
// the original transaction was restored
|
||||
Transaction restored = Ebean.currentTransaction();
|
||||
assertSame(currentTxn, restored);
|
||||
assertSame(currentTxn, innerTxn);
|
||||
assertSame(currentTxn, outerTxn);
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
}
|
||||
|
||||
assertNull(Ebean.currentTransaction());
|
||||
}
|
||||
|
||||
class OuterTransactionalWithRequired {
|
||||
|
||||
@Transactional(type = TxType.REQUIRED)
|
||||
void doOuter() {
|
||||
outerTxn = Ebean.currentTransaction();
|
||||
log.info("outer ...{}", outerTxn);
|
||||
|
||||
new InTransactionalWithRequired().doInner();
|
||||
|
||||
Transaction current = Ebean.currentTransaction();
|
||||
assertSame(outerTxn, current);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InTransactionalWithRequired {
|
||||
|
||||
@Transactional(type = TxType.REQUIRED)
|
||||
void doInner() {
|
||||
innerTxn = Ebean.currentTransaction();
|
||||
log.info("inner ...{}", innerTxn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.tests.transaction;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import io.ebean.annotation.TxType;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class TestTransactionalRequiresNew extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestTransactionalRequiresNew.class);
|
||||
|
||||
private Connection outerConn;
|
||||
private Transaction outerTxn;
|
||||
|
||||
@Test
|
||||
public void basic() {
|
||||
|
||||
outerTxn = null;
|
||||
assertNull(Ebean.currentTransaction());
|
||||
|
||||
new OuterTransactionalWithRequired().doOuter();
|
||||
|
||||
assertNull(Ebean.currentTransaction());
|
||||
assertNotNull(outerTxn);
|
||||
}
|
||||
|
||||
|
||||
class OuterTransactionalWithRequired {
|
||||
|
||||
@Transactional
|
||||
void doOuter() {
|
||||
outerTxn = Ebean.currentTransaction();
|
||||
log.info("outer before ...{}", outerTxn);
|
||||
outerConn = outerTxn.getConnection();
|
||||
|
||||
new InTransactionalWithRequiresNew().doInner();
|
||||
|
||||
// restore the outerTxn
|
||||
Transaction current = Ebean.currentTransaction();
|
||||
log.info("outer after ...{}", current);
|
||||
assertSame(outerConn, current.getConnection());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InTransactionalWithRequiresNew {
|
||||
|
||||
@Transactional(type = TxType.REQUIRES_NEW)
|
||||
void doInner() {
|
||||
Transaction innerTxn = Ebean.currentTransaction();
|
||||
log.info("inner ...{} {}", innerTxn);
|
||||
|
||||
Connection connection = innerTxn.getConnection();
|
||||
assertNotSame(connection, outerConn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.tests.transaction;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Transactional;
|
||||
import io.ebean.annotation.TxType;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class TestTransactionalSupports extends BaseTestCase {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TestTransactionalSupports.class);
|
||||
|
||||
private Transaction outerTxn;
|
||||
|
||||
@Test
|
||||
public void noCurrentTransaction() {
|
||||
|
||||
outerTxn = null;
|
||||
new SomeTransactionalWithSupports().doStuff();
|
||||
assertNull(outerTxn);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void withOuterTransaction_expect_currentTransactionAvailable() {
|
||||
|
||||
outerTxn = null;
|
||||
Ebean.beginTransaction();
|
||||
try {
|
||||
new SomeTransactionalWithSupports().doStuff();
|
||||
assertNotNull(outerTxn);
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
class SomeTransactionalWithSupports {
|
||||
|
||||
@Transactional(type = TxType.SUPPORTS)
|
||||
void doStuff() {
|
||||
outerTxn = Ebean.currentTransaction();
|
||||
log.info("outer ...{}", outerTxn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
<logger name="io.ebean.cache.NATKEY" level="TRACE"/>
|
||||
<logger name="io.ebean.cache.COLL" level="TRACE"/>
|
||||
|
||||
<!--<logger name="org.avaje.classpath" level="INFO"/>-->
|
||||
<!--<logger name="io.ebeaninternal.server.deploy" level="TRACE"/>-->
|
||||
|
||||
<!--<logger name="io.ebeaninternal.server.autotune" level="TRACE"/>-->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user