mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64405ade52 | ||
|
|
ce5f1b25f5 | ||
|
|
ae4f50e7b3 | ||
|
|
c9120be347 | ||
|
|
b189e67b4c | ||
|
|
5d5c275c79 | ||
|
|
52f31a7a8e | ||
|
|
6697aecd48 | ||
|
|
348c075a14 | ||
|
|
bf2420702c | ||
|
|
16832ac67a | ||
|
|
47bd2ff03d | ||
|
|
850f1c1469 | ||
|
|
622a90c7d5 | ||
|
|
4c317f11ae | ||
|
|
01bce5cfa6 | ||
|
|
2158925d3c | ||
|
|
2aff6403a2 | ||
|
|
3c4d600187 | ||
|
|
f60ccb9b90 | ||
|
|
2a6108087c | ||
|
|
0da86a82ae | ||
|
|
643b0499c4 | ||
|
|
676bac390e | ||
|
|
79740accfa | ||
|
|
77c6f1cb9f | ||
|
|
538361d5fd | ||
|
|
b17593ba3c | ||
|
|
eb00690b30 | ||
|
|
a772e65d32 | ||
|
|
66f7f69ec7 | ||
|
|
ad52711fe1 | ||
|
|
d6fdb4ee34 | ||
|
|
74c7268d2f | ||
|
|
42fc419759 | ||
|
|
4fb3b9cddc | ||
|
|
c60077174e | ||
|
|
5e893dd2ad | ||
|
|
9eb54da2dc | ||
|
|
bb6ff866c7 | ||
|
|
2d719c5b37 | ||
|
|
9d54b264ad | ||
|
|
65b282a056 | ||
|
|
fc174c8988 | ||
|
|
06802896ef | ||
|
|
7f13ac9e5e | ||
|
|
4a1374d22d | ||
|
|
4ee8d647b7 | ||
|
|
59793f3c01 | ||
|
|
f7f383d8f3 | ||
|
|
562a3959b6 | ||
|
|
bb1275f4f9 | ||
|
|
7fde6b9331 | ||
|
|
3e97a4005a | ||
|
|
c271760230 | ||
|
|
f7c1845f0c | ||
|
|
9b04f20ed3 | ||
|
|
62b6b6293b | ||
|
|
6d8a1c9398 | ||
|
|
303039735f | ||
|
|
6bcf5c6881 | ||
|
|
0527f45f5a | ||
|
|
ba397bbb0c | ||
|
|
a617d58902 | ||
|
|
7fc4a229ae | ||
|
|
f53d31aa2e | ||
|
|
b889b6a6b0 | ||
|
|
f9b55a06b8 | ||
|
|
09d772a5e5 | ||
|
|
f7035eb228 | ||
|
|
1bf09ddd38 | ||
|
|
a8bf27da55 | ||
|
|
ee8bb5ef75 | ||
|
|
bcfd9cab1d | ||
|
|
2a5fd7f3a9 | ||
|
|
f8c37772d4 | ||
|
|
d988dbe69d | ||
|
|
5cb9b9b237 | ||
|
|
819d391495 | ||
|
|
1f0ed629a7 | ||
|
|
91044d0a75 | ||
|
|
57736185d0 | ||
|
|
8992980689 | ||
|
|
f341ff0167 | ||
|
|
abfa841ed7 | ||
|
|
e68adf53bc | ||
|
|
562d175de1 | ||
|
|
b5cef354ad | ||
|
|
09a887aeb7 | ||
|
|
53c903fa48 | ||
|
|
548454c90d | ||
|
|
94bab5f83b | ||
|
|
78965c1ac4 | ||
|
|
00b0fcaeb3 | ||
|
|
f7905c8e94 |
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -50,7 +50,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>6.15</version>
|
||||
<version>7.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -78,7 +78,7 @@ public class DatabaseFactory {
|
||||
Database server = createInternal(config);
|
||||
if (config.isRegister()) {
|
||||
if (config.isDefaultServer()) {
|
||||
if (defaultServerName != null) {
|
||||
if (defaultServerName != null && !defaultServerName.equals(config.getName())) {
|
||||
throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default");
|
||||
}
|
||||
defaultServerName = config.getName();
|
||||
|
||||
@@ -1671,7 +1671,7 @@ public interface ExpressionList<T> {
|
||||
ExpressionList<T> endAnd();
|
||||
|
||||
/**
|
||||
* End a AND junction - synonym for endJunction().
|
||||
* End a OR junction - synonym for endJunction().
|
||||
*/
|
||||
ExpressionList<T> endOr();
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class FetchConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
|
||||
*
|
||||
* <p>
|
||||
* Construct using default JOIN mode.
|
||||
*/
|
||||
@Deprecated
|
||||
@@ -89,7 +89,7 @@ public class FetchConfig implements Serializable {
|
||||
* Return FetchConfig to lazily load the relationship.
|
||||
*/
|
||||
public static FetchConfig ofLazy() {
|
||||
return new FetchConfig(LAZY_MODE, 10);
|
||||
return new FetchConfig(LAZY_MODE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +110,8 @@ public class FetchConfig implements Serializable {
|
||||
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
|
||||
*/
|
||||
private FetchConfig mutate(int mode, int batchSize) {
|
||||
if (batchSize < 1) {
|
||||
throw new IllegalArgumentException("batch size "+batchSize+" must be > 0");
|
||||
if (batchSize < 0) {
|
||||
throw new IllegalArgumentException("batch size " + batchSize + " must be > 0");
|
||||
}
|
||||
this.mode = mode;
|
||||
this.batchSize = batchSize;
|
||||
@@ -124,7 +124,7 @@ public class FetchConfig implements Serializable {
|
||||
*/
|
||||
@Deprecated
|
||||
public FetchConfig lazy() {
|
||||
return mutate(LAZY_MODE, 10);
|
||||
return mutate(LAZY_MODE, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,7 +137,7 @@ public class FetchConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to FetchConfig.ofQuery().
|
||||
*
|
||||
* <p>
|
||||
* Eagerly fetch the beans in this path as a separate query (rather than as
|
||||
* part of the main query).
|
||||
* <p>
|
||||
@@ -150,17 +150,15 @@ public class FetchConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
|
||||
*
|
||||
* <p>
|
||||
* Eagerly fetch the beans in this path as a separate query (rather than as
|
||||
* part of the main query).
|
||||
* <p>
|
||||
* The queryBatchSize is the number of parent id's that this separate query
|
||||
* will load per batch.
|
||||
* </p>
|
||||
* <p>
|
||||
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
|
||||
* is also used.
|
||||
* </p>
|
||||
*
|
||||
* @param batchSize the batch size used to load beans on this path
|
||||
*/
|
||||
@@ -171,13 +169,12 @@ public class FetchConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
|
||||
*
|
||||
* <p>
|
||||
* Eagerly fetch the first batch of beans on this path.
|
||||
* This is similar to {@link #query(int)} but only fetches the first batch.
|
||||
* <p>
|
||||
* If there are more parent beans than the batch size then they will not be
|
||||
* loaded eagerly but instead use lazy loading.
|
||||
* </p>
|
||||
*
|
||||
* @param batchSize the number of parent beans this path is populated for
|
||||
*/
|
||||
@@ -188,7 +185,7 @@ public class FetchConfig implements Serializable {
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to FetchConfig.ofCache().
|
||||
*
|
||||
* <p>
|
||||
* Eagerly fetch the beans fetching the beans from the L2 bean cache
|
||||
* and using the DB for beans not in the cache.
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ import java.util.Objects;
|
||||
* on the Query object.
|
||||
* </p>
|
||||
*/
|
||||
public final class OrderBy<T> implements Serializable {
|
||||
public class OrderBy<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 9157089257745730539L;
|
||||
|
||||
@@ -69,7 +69,6 @@ public final class OrderBy<T> implements Serializable {
|
||||
* Add a property with ascending order to this OrderBy.
|
||||
*/
|
||||
public Query<T> asc(String propertyName) {
|
||||
|
||||
list.add(new Property(propertyName, true));
|
||||
return query;
|
||||
}
|
||||
@@ -98,7 +97,6 @@ public final class OrderBy<T> implements Serializable {
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the property is known to be contained in the order by clause.
|
||||
*/
|
||||
@@ -207,7 +205,6 @@ public final class OrderBy<T> implements Serializable {
|
||||
if (!(obj instanceof OrderBy<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OrderBy<?> e = (OrderBy<?>) obj;
|
||||
return e.list.equals(list);
|
||||
}
|
||||
@@ -249,7 +246,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
/**
|
||||
* A property and its ascending descending order.
|
||||
*/
|
||||
public static final class Property implements Serializable {
|
||||
public static class Property implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1546009780322478077L;
|
||||
|
||||
@@ -415,13 +412,10 @@ public final class OrderBy<T> implements Serializable {
|
||||
}
|
||||
|
||||
private void parse(String orderByClause) {
|
||||
|
||||
if (orderByClause == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String[] chunks = orderByClause.split(",");
|
||||
for (String chunk : chunks) {
|
||||
for (String chunk : orderByClause.split(",")) {
|
||||
Property p = parseProperty(chunk);
|
||||
if (p != null) {
|
||||
list.add(p);
|
||||
@@ -467,8 +461,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
if (s.startsWith("desc")) {
|
||||
return false;
|
||||
}
|
||||
String m = "Expecting [" + s + "] to be asc or desc?";
|
||||
throw new RuntimeException(m);
|
||||
throw new RuntimeException("Expecting [" + s + "] to be asc or desc?");
|
||||
}
|
||||
|
||||
private boolean isEmptyString(String s) {
|
||||
|
||||
@@ -59,6 +59,14 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void register(TransactionCallback callback);
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL - turn on automatic persistence of dirty beans and batchMode true.
|
||||
* <p>
|
||||
* With this turned on beans that are dirty in the persistence context
|
||||
* are automatically persisted on flush() and commit().
|
||||
*/
|
||||
void setAutoPersistUpdates(boolean autoPersistUpdates);
|
||||
|
||||
/**
|
||||
* Set a label on the transaction.
|
||||
* <p>
|
||||
@@ -339,15 +347,12 @@ public interface Transaction extends AutoCloseable {
|
||||
* The batch is automatically flushed when it hits the batch size and also when we
|
||||
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
|
||||
* beans.
|
||||
* </p>
|
||||
* <p>
|
||||
* We use {@link #flush()} to explicitly flush the batch and we can use
|
||||
* {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)}
|
||||
* to control the automatic flushing behaviour.
|
||||
* </p>
|
||||
* <p>
|
||||
* Example: batch processing of CallableSql executing every 10 rows
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -392,14 +397,11 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* This only takes effect when batch mode on the transaction has not already meant that
|
||||
* JDBC batch mode is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
|
||||
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is effectively already turned on for all platforms apart from older Sql Server.
|
||||
* </p>
|
||||
*
|
||||
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch)
|
||||
@@ -422,15 +424,19 @@ public interface Transaction extends AutoCloseable {
|
||||
int getBatchSize();
|
||||
|
||||
/**
|
||||
* Specify if you want batched inserts to use getGeneratedKeys.
|
||||
* Specify if we want batched inserts to use getGeneratedKeys.
|
||||
* <p>
|
||||
* By default batched inserts will try to use getGeneratedKeys if it is
|
||||
* supported by the underlying jdbc driver and database.
|
||||
* </p>
|
||||
* <p>
|
||||
* You may want to turn getGeneratedKeys off when you are inserting a large
|
||||
* number of objects and you don't care about getting back the ids.
|
||||
* </p>
|
||||
* We want to turn off getGeneratedKeys when we are inserting a large
|
||||
* number of objects and we don't care about getting back the ids. In this
|
||||
* way we avoid the extra cost of getting back the generated id values
|
||||
* from the database.
|
||||
* <p>
|
||||
* Note that when we do turn off getGeneratedKeys then we have the limitation
|
||||
* that after a bean has been inserted we are unable to then mutate the bean
|
||||
* and update it in the same transaction as we have not obtained it's id value.
|
||||
*/
|
||||
void setGetGeneratedKeys(boolean getGeneratedKeys);
|
||||
|
||||
@@ -449,13 +455,11 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* If you want to execute both WITHOUT having the batch automatically flush
|
||||
* you need to call this with batchFlushOnMixed = false.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
|
||||
* beans are executed). This is because the UpdateSql and CallableSql have
|
||||
* already been bound to their PreparedStatements. The beans on the other hand
|
||||
* have a 2 step process (delayed binding).
|
||||
* </p>
|
||||
*/
|
||||
void setFlushOnMixed(boolean batchFlushOnMixed);
|
||||
|
||||
@@ -473,7 +477,6 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* Calling this method with batchFlushOnQuery = false means that you can
|
||||
* execute a query and the batch will not be automatically flushed.
|
||||
* </p>
|
||||
*/
|
||||
void setFlushOnQuery(boolean batchFlushOnQuery);
|
||||
|
||||
@@ -490,7 +493,6 @@ public interface Transaction extends AutoCloseable {
|
||||
* should be flushed prior to executing a query.
|
||||
* <p>
|
||||
* The default is for this to be true.
|
||||
* </p>
|
||||
*/
|
||||
boolean isFlushOnQuery();
|
||||
|
||||
@@ -507,7 +509,6 @@ public interface Transaction extends AutoCloseable {
|
||||
* flush the batch if you like.
|
||||
* <p>
|
||||
* Flushing occurs automatically when:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>the batch size is reached</li>
|
||||
* <li>A query is executed on the same transaction</li>
|
||||
@@ -533,11 +534,9 @@ public interface Transaction extends AutoCloseable {
|
||||
* commit() rollback() and end() methods on the Transaction should still be
|
||||
* used. Calling these methods on the Connection would be a big no no unless
|
||||
* you know what you are doing.
|
||||
* </p>
|
||||
* <p>
|
||||
* Examples of when a developer may wish to use the connection directly are:
|
||||
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
|
||||
* </p>
|
||||
*/
|
||||
Connection getConnection();
|
||||
|
||||
@@ -545,17 +544,14 @@ public interface Transaction extends AutoCloseable {
|
||||
* Add table modification information to the TransactionEvent.
|
||||
* <p>
|
||||
* Use this in conjunction with getConnection() and raw JDBC.
|
||||
* </p>
|
||||
* <p>
|
||||
* This effectively informs Ebean of the data that has been changed by the
|
||||
* transaction and this information is normally automatically handled by Ebean
|
||||
* when you save entity beans or use UpdateSql etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you use raw JDBC then you can use this method to inform Ebean for the
|
||||
* tables that have been modified. Ebean uses this information to keep its
|
||||
* caches in synch and maintain text indexes.
|
||||
* </p>
|
||||
*/
|
||||
void addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebean;
|
||||
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.annotation.TxOption;
|
||||
import io.ebean.annotation.TxType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -33,6 +34,8 @@ public final class TxScope {
|
||||
|
||||
private TxIsolation isolation;
|
||||
|
||||
private TxOption autoPersistUpdates;
|
||||
|
||||
private PersistBatch batch;
|
||||
|
||||
private PersistBatch batchOnCascade;
|
||||
@@ -123,6 +126,13 @@ public final class TxScope {
|
||||
+ "] serverName[" + serverName + "] rollbackFor[" + rollbackFor + "] noRollbackFor[" + noRollbackFor + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the AutoPersistUpdates mode as a nullable Boolean.
|
||||
*/
|
||||
public Boolean getAutoPersistUpdates() {
|
||||
return autoPersistUpdates == null ? null : autoPersistUpdates.asBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if PersistBatch has been set.
|
||||
*/
|
||||
@@ -176,6 +186,14 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the autoPersistUpdates mode.
|
||||
*/
|
||||
public TxScope setAutoPersistUpdates(TxOption autoPersistUpdates) {
|
||||
this.autoPersistUpdates = autoPersistUpdates;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction profile id.
|
||||
*/
|
||||
|
||||
@@ -85,6 +85,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private static final byte FLAG_CHANGED_PROP = 2;
|
||||
|
||||
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
|
||||
|
||||
/**
|
||||
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
|
||||
* between an embedded bean being completely overwritten and one of its
|
||||
@@ -569,6 +571,10 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
flags[propertyIndex] |= FLAG_CHANGED_PROP;
|
||||
}
|
||||
|
||||
private void setChangeLoaded(int propertyIndex) {
|
||||
flags[propertyIndex] |= FLAG_CHANGEDLOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that an embedded bean has had one of its properties changed.
|
||||
*/
|
||||
@@ -937,7 +943,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("This bean is readOnly");
|
||||
}
|
||||
setChangedProperty(propertyIndex);
|
||||
setChangeLoaded(propertyIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean.bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds entity beans by there type and id.
|
||||
* <p>
|
||||
@@ -77,6 +79,11 @@ public interface PersistenceContext {
|
||||
*/
|
||||
boolean resetLimit();
|
||||
|
||||
/**
|
||||
* Return the list of dirty beans held by this persistence context.
|
||||
*/
|
||||
List<Object> dirtyBeans();
|
||||
|
||||
/**
|
||||
* Wrapper on a bean to also indicate if a bean has been deleted.
|
||||
* <p>
|
||||
|
||||
@@ -198,10 +198,9 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
if (list == null) {
|
||||
sb.append("deferred ");
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(list.size()).append("] ");
|
||||
sb.append("list").append(list).append("");
|
||||
sb.append("list").append(list);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -175,10 +175,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
|
||||
/**
|
||||
* Returns the map entrySet.
|
||||
* <p>
|
||||
* This is because the key values may need to be set against the details (so
|
||||
* they don't need to be set twice).
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public Collection<?> getActualEntries() {
|
||||
@@ -194,7 +190,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
}
|
||||
if (map == null) {
|
||||
sb.append("deferred ");
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(map.size()).append("]");
|
||||
sb.append(" map").append(map);
|
||||
@@ -243,17 +238,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Set<Entry<K, E>> entrySet() {
|
||||
init();
|
||||
if (isReadOnly()) {
|
||||
return Collections.unmodifiableSet(map.entrySet());
|
||||
}
|
||||
if (modifyListening) {
|
||||
Set<Entry<K, E>> s = map.entrySet();
|
||||
return new ModifySet(this, s);
|
||||
}
|
||||
return map.entrySet();
|
||||
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,8 +264,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
if (isReadOnly()) {
|
||||
return Collections.unmodifiableSet(map.keySet());
|
||||
}
|
||||
// we don't really care about modifications to the ketSet?
|
||||
return map.keySet();
|
||||
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -346,11 +335,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
if (isReadOnly()) {
|
||||
return Collections.unmodifiableCollection(map.values());
|
||||
}
|
||||
if (modifyListening) {
|
||||
Collection<E> c = map.values();
|
||||
return new ModifyCollection<>(this, c);
|
||||
}
|
||||
return map.values();
|
||||
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -176,7 +176,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
}
|
||||
if (set == null) {
|
||||
sb.append("deferred ");
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(set.size()).append("]");
|
||||
sb.append(" set").append(set);
|
||||
|
||||
@@ -25,7 +25,7 @@ class ModifyCollection<E> implements Collection<E> {
|
||||
* The owner is notified of the additions and removals.
|
||||
* </p>
|
||||
*/
|
||||
public ModifyCollection(BeanCollection<E> owner, Collection<E> c) {
|
||||
ModifyCollection(BeanCollection<E> owner, Collection<E> c) {
|
||||
this.owner = owner;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Handles the Entry Set for BeanMap.
|
||||
*/
|
||||
class ModifyEntrySet<K, E> implements Set<Map.Entry<K, E>> {
|
||||
|
||||
private final BeanMap<K, E> owner;
|
||||
private final Set<Map.Entry<K, E>> entrySet;
|
||||
|
||||
ModifyEntrySet(BeanMap<K, E> owner, Set<Map.Entry<K, E>> entrySet) {
|
||||
this.owner = owner;
|
||||
this.entrySet = entrySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return entrySet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return entrySet.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return entrySet.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return entrySet.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return entrySet.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> entries) {
|
||||
return entrySet.containsAll(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
owner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(Map.Entry<K, E> entry) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends Map.Entry<K, E>> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
if (o instanceof Map.Entry) {
|
||||
Map.Entry entry = (Map.Entry) o;
|
||||
final E val = owner.get(entry.getKey());
|
||||
if (Objects.equals(val, entry.getValue())) {
|
||||
owner.remove(entry.getKey());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> entries) {
|
||||
boolean modified = false;
|
||||
final Iterator<Map.Entry<K, E>> it = iterator();
|
||||
while (it.hasNext()) {
|
||||
if (!entries.contains(it.next())) {
|
||||
it.remove();
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> entries) {
|
||||
boolean modified = false;
|
||||
for (Object entry : entries) {
|
||||
modified |= remove(entry);
|
||||
}
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, E>> iterator() {
|
||||
return new EntrySetIterator(new ArrayList<>(entrySet).iterator());
|
||||
}
|
||||
|
||||
class EntrySetIterator implements Iterator<Map.Entry<K, E>> {
|
||||
|
||||
private final Iterator<Map.Entry<K, E>> iterator;
|
||||
private Map.Entry<K, E> entry;
|
||||
|
||||
EntrySetIterator(Iterator<Map.Entry<K, E>> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map.Entry<K, E> next() {
|
||||
entry = iterator.next();
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
owner.remove(entry.getKey());
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Handle the Key Set for BeanMap.
|
||||
*/
|
||||
class ModifyKeySet<E> implements Set<E> {
|
||||
|
||||
private final Set<E> keySet;
|
||||
private final BeanMap<E, ?> owner;
|
||||
|
||||
ModifyKeySet(BeanMap<E, ?> owner, Set<E> keySet) {
|
||||
this.owner = owner;
|
||||
this.keySet = keySet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return keySet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return keySet.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return keySet.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return keySet.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return keySet.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E key) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> keys) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return owner.remove(o) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> keys) {
|
||||
return keySet.containsAll(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
owner.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new KeySetIterator<>(new ArrayList<>(keySet).iterator());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> keys) {
|
||||
return keysMatch(keys, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> keys) {
|
||||
return keysMatch(keys, true);
|
||||
}
|
||||
|
||||
private boolean keysMatch(Collection<?> keys, boolean containsMatch) {
|
||||
boolean changed = false;
|
||||
final Iterator<E> iterator = iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final E key = iterator.next();
|
||||
if (keys.contains(key) == containsMatch) {
|
||||
iterator.remove();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
class KeySetIterator<K> implements Iterator<K> {
|
||||
|
||||
private final Iterator<K> iterator;
|
||||
private K key;
|
||||
|
||||
KeySetIterator(Iterator<K> iterator) {
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public K next() {
|
||||
key = iterator.next();
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
owner.remove(key);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Wraps a Set for the purposes of notifying removals and additions to the
|
||||
* BeanCollection owner.
|
||||
* <p>
|
||||
* This is required for persisting ManyToMany objects. Additions and removals
|
||||
* become inserts and deletes to the intersection table.
|
||||
* </p>
|
||||
*/
|
||||
class ModifySet<E> extends ModifyCollection<E> implements Set<E> {
|
||||
|
||||
/**
|
||||
* Create with an Owner that is notified of any additions or deletions.
|
||||
*/
|
||||
public ModifySet(BeanCollection<E> owner, Set<E> s) {
|
||||
super(owner, s);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -232,6 +232,12 @@ public class DatabaseConfig {
|
||||
*/
|
||||
private String historyTableSuffix = "_history";
|
||||
|
||||
/**
|
||||
* When true explicit transactions beans that have been made dirty will be
|
||||
* automatically persisted via update on flush.
|
||||
*/
|
||||
private boolean autoPersistUpdates;
|
||||
|
||||
/**
|
||||
* Use for transaction scoped batch mode.
|
||||
*/
|
||||
@@ -498,14 +504,23 @@ public class DatabaseConfig {
|
||||
private boolean notifyL2CacheInForeground;
|
||||
|
||||
/**
|
||||
* Set to true to support query plan capture.
|
||||
* Set to true to enable bind capture required for query plan capture.
|
||||
*/
|
||||
private boolean collectQueryPlans;
|
||||
private boolean queryPlanEnable;
|
||||
|
||||
/**
|
||||
* The default threshold in micros for collecting query plans.
|
||||
*/
|
||||
private long collectQueryPlanThresholdMicros = Long.MAX_VALUE;
|
||||
private long queryPlanThresholdMicros = Long.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Set to true to enable automatic periodic query plan capture.
|
||||
*/
|
||||
private boolean queryPlanCapture;
|
||||
private long queryPlanCapturePeriodSecs = 60 * 10; // 10 minutes
|
||||
private long queryPlanCaptureMaxTimeMillis = 10_000; // 10 seconds
|
||||
private int queryPlanCaptureMaxCount = 10;
|
||||
private QueryPlanListener queryPlanListener;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
@@ -896,6 +911,20 @@ public class DatabaseConfig {
|
||||
this.tenantCatalogProvider = tenantCatalogProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if dirty beans are automatically persisted.
|
||||
*/
|
||||
public boolean isAutoPersistUpdates() {
|
||||
return autoPersistUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if dirty beans are automatically persisted.
|
||||
*/
|
||||
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
|
||||
this.autoPersistUpdates = autoPersistUpdates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PersistBatch mode to use by default at the transaction level.
|
||||
* <p>
|
||||
@@ -1045,7 +1074,6 @@ public class DatabaseConfig {
|
||||
* This is a performance optimisation to reduce the number times Ebean
|
||||
* requests a sequence to be used as an Id for a bean (aka reduce network
|
||||
* chatter).
|
||||
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
|
||||
@@ -2693,7 +2721,10 @@ public class DatabaseConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from ebean.properties.
|
||||
* Load settings from application.properties, application.yaml and other sources.
|
||||
* <p>
|
||||
* Uses <code>avaje-config</code> to load configuration properties. Goto https://avaje.io/config
|
||||
* for detail on how and where properties are loaded from.
|
||||
*/
|
||||
public void loadFromProperties() {
|
||||
this.properties = Config.asProperties();
|
||||
@@ -2796,14 +2827,19 @@ public class DatabaseConfig {
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
defaultServer = p.getBoolean("defaultServer", defaultServer);
|
||||
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
|
||||
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
|
||||
maxCallStack = p.getInt("maxCallStack", maxCallStack);
|
||||
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
|
||||
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
|
||||
collectQueryPlanThresholdMicros = p.getLong("collectQueryPlanThresholdMicros", collectQueryPlanThresholdMicros);
|
||||
queryPlanEnable = p.getBoolean("queryPlan.enable", queryPlanEnable);
|
||||
queryPlanThresholdMicros = p.getLong("queryPlan.thresholdMicros", queryPlanThresholdMicros);
|
||||
queryPlanCapture = p.getBoolean("queryPlan.capture", queryPlanCapture);
|
||||
queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
|
||||
queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
|
||||
queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
|
||||
@@ -3093,14 +3129,17 @@ public class DatabaseConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan time to live.
|
||||
* Return the time to live for ebean's internal query plan.
|
||||
*/
|
||||
public int getQueryPlanTTLSeconds() {
|
||||
return queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query plan time to live.
|
||||
* Set the time to live for ebean's internal query plan.
|
||||
* <p>
|
||||
* This is the plan that knows how to execute the query, read the result
|
||||
* and collects execution metrics. By default this is set to 5 mins.
|
||||
*/
|
||||
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
|
||||
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
|
||||
@@ -3173,29 +3212,110 @@ public class DatabaseConfig {
|
||||
/**
|
||||
* Return true if query plan capture is enabled.
|
||||
*/
|
||||
public boolean isCollectQueryPlans() {
|
||||
return collectQueryPlans;
|
||||
public boolean isQueryPlanEnable() {
|
||||
return queryPlanEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable query plan capture.
|
||||
*/
|
||||
public void setCollectQueryPlans(boolean collectQueryPlans) {
|
||||
this.collectQueryPlans = collectQueryPlans;
|
||||
public void setQueryPlanEnable(boolean queryPlanEnable) {
|
||||
this.queryPlanEnable = queryPlanEnable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan collection threshold in microseconds.
|
||||
*/
|
||||
public long getCollectQueryPlanThresholdMicros() {
|
||||
return collectQueryPlanThresholdMicros;
|
||||
public long getQueryPlanThresholdMicros() {
|
||||
return queryPlanThresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query plan collection threshold in microseconds.
|
||||
* <p>
|
||||
* Queries executing slower than this will have bind values captured such that later
|
||||
* the query plan can be captured and reported.
|
||||
*/
|
||||
public void setCollectQueryPlanThresholdMicros(long collectQueryPlanThresholdMicros) {
|
||||
this.collectQueryPlanThresholdMicros = collectQueryPlanThresholdMicros;
|
||||
public void setQueryPlanThresholdMicros(long queryPlanThresholdMicros) {
|
||||
this.queryPlanThresholdMicros = queryPlanThresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if periodic capture of query plans is enabled.
|
||||
*/
|
||||
public boolean isQueryPlanCapture() {
|
||||
return queryPlanCapture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to turn on periodic capture of query plans.
|
||||
*/
|
||||
public void setQueryPlanCapture(boolean queryPlanCapture) {
|
||||
this.queryPlanCapture = queryPlanCapture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the frequency to capture query plans.
|
||||
*/
|
||||
public long getQueryPlanCapturePeriodSecs() {
|
||||
return queryPlanCapturePeriodSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the frequency in seconds to capture query plans.
|
||||
*/
|
||||
public void setQueryPlanCapturePeriodSecs(long queryPlanCapturePeriodSecs) {
|
||||
this.queryPlanCapturePeriodSecs = queryPlanCapturePeriodSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time after which a capture query plans request will
|
||||
* stop capturing more query plans.
|
||||
* <p>
|
||||
* Effectively this controls the amount of load/time we want to
|
||||
* allow for query plan capture.
|
||||
*/
|
||||
public long getQueryPlanCaptureMaxTimeMillis() {
|
||||
return queryPlanCaptureMaxTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time after which a capture query plans request will
|
||||
* stop capturing more query plans.
|
||||
* <p>
|
||||
* Effectively this controls the amount of load/time we want to
|
||||
* allow for query plan capture.
|
||||
*/
|
||||
public void setQueryPlanCaptureMaxTimeMillis(long queryPlanCaptureMaxTimeMillis) {
|
||||
this.queryPlanCaptureMaxTimeMillis = queryPlanCaptureMaxTimeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the max number of query plans captured per request.
|
||||
*/
|
||||
public int getQueryPlanCaptureMaxCount() {
|
||||
return queryPlanCaptureMaxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the max number of query plans captured per request.
|
||||
*/
|
||||
public void setQueryPlanCaptureMaxCount(int queryPlanCaptureMaxCount) {
|
||||
this.queryPlanCaptureMaxCount = queryPlanCaptureMaxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the listener used to process captured query plans.
|
||||
*/
|
||||
public QueryPlanListener getQueryPlanListener() {
|
||||
return queryPlanListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener used to process captured query plans.
|
||||
*/
|
||||
public void setQueryPlanListener(QueryPlanListener queryPlanListener) {
|
||||
this.queryPlanListener = queryPlanListener;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3249,7 +3369,7 @@ public class DatabaseConfig {
|
||||
this.loadModuleInfo = loadModuleInfo;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
VERSION1RND
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.Database;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The captured query plans.
|
||||
*/
|
||||
public class QueryPlanCapture {
|
||||
|
||||
private final Database database;
|
||||
private final List<MetaQueryPlan> plans;
|
||||
|
||||
public QueryPlanCapture(Database database, List<MetaQueryPlan> plans) {
|
||||
this.database = database;
|
||||
this.plans = plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database the plans were captured for.
|
||||
*/
|
||||
public Database getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the captured query plans.
|
||||
*/
|
||||
public List<MetaQueryPlan> getPlans() {
|
||||
return plans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* EXPERIMENTAL: Listener for captured query plans.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface QueryPlanListener {
|
||||
|
||||
/**
|
||||
* Process the captured query plans.
|
||||
*/
|
||||
void process(QueryPlanCapture capture);
|
||||
}
|
||||
@@ -4,11 +4,7 @@ import org.h2.api.Trigger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -43,7 +39,6 @@ public class H2HistoryTrigger implements Trigger {
|
||||
|
||||
@Override
|
||||
public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
|
||||
|
||||
// get the columns for the table
|
||||
ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null);
|
||||
|
||||
@@ -79,7 +74,6 @@ public class H2HistoryTrigger implements Trigger {
|
||||
|
||||
@Override
|
||||
public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException {
|
||||
|
||||
if (oldRow != null) {
|
||||
// a delete or update event
|
||||
Timestamp now = new Timestamp(System.currentTimeMillis());
|
||||
@@ -99,7 +93,6 @@ public class H2HistoryTrigger implements Trigger {
|
||||
* Insert the data into the history table.
|
||||
*/
|
||||
private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException {
|
||||
|
||||
try (PreparedStatement stmt = connection.prepareStatement(insertHistorySql)) {
|
||||
for (int i = 0; i < oldRow.length; i++) {
|
||||
stmt.setObject(i + 1, oldRow[i]);
|
||||
@@ -110,11 +103,11 @@ public class H2HistoryTrigger implements Trigger {
|
||||
|
||||
@Override
|
||||
public void close() throws SQLException {
|
||||
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() throws SQLException {
|
||||
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@ public class QueryPlanRequest {
|
||||
|
||||
/**
|
||||
* Set the maximum number of plans to capture.
|
||||
* <p>
|
||||
* Use this to limit how much query plan capturing is done as query
|
||||
* plan capture is actual database load.
|
||||
*/
|
||||
public void setMaxCount(int maxCount) {
|
||||
this.maxCount = maxCount;
|
||||
@@ -58,7 +61,9 @@ public class QueryPlanRequest {
|
||||
/**
|
||||
* Set the maximum amount of time we want to use to capture plans.
|
||||
* <p>
|
||||
* Query plan collection will stop once this time is exceeded.
|
||||
* Query plan collection will stop once this time is exceeded. We use
|
||||
* this to ensure the query plan capture does not use excessive amount
|
||||
* of time - put too much load on the database.
|
||||
*/
|
||||
public void setMaxTimeMillis(long maxTimeMillis) {
|
||||
this.maxTimeMillis = maxTimeMillis;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.8.0</tag>
|
||||
<tag>ebean-parent-12.9.1</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean autotune</name>
|
||||
@@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
+1
-9
@@ -46,7 +46,6 @@ public class ProfileManager implements ProfilingListener {
|
||||
|
||||
@Override
|
||||
public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query) {
|
||||
|
||||
ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey());
|
||||
if (profileOrigin == null) {
|
||||
profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query));
|
||||
@@ -61,12 +60,11 @@ public class ProfileManager implements ProfilingListener {
|
||||
* <p>
|
||||
* For new profiling entries it is useful to compare the profiling against the current
|
||||
* query detail that is specified in the code (as the query might already be manually optimised).
|
||||
* </p>
|
||||
*/
|
||||
private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery<?> query) {
|
||||
ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate);
|
||||
// set the current query detail (fetch group) so that we can compare against profiling for new entries
|
||||
profileOrigin.setOriginalQuery(query.getDetail().toString());
|
||||
profileOrigin.setOriginalQuery(query.getDetail().asString());
|
||||
return profileOrigin;
|
||||
}
|
||||
|
||||
@@ -77,7 +75,6 @@ public class ProfileManager implements ProfilingListener {
|
||||
*/
|
||||
@Override
|
||||
public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) {
|
||||
|
||||
if (node != null) {
|
||||
ObjectGraphOrigin origin = node.getOriginQueryPoint();
|
||||
if (origin != null) {
|
||||
@@ -92,11 +89,9 @@ public class ProfileManager implements ProfilingListener {
|
||||
* <p>
|
||||
* This is sent to use from a EntityBeanIntercept when the finalise method
|
||||
* is called on the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void collectNodeUsage(NodeUsageCollector usageCollector) {
|
||||
|
||||
ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint());
|
||||
profileOrigin.collectUsageInfo(usageCollector);
|
||||
}
|
||||
@@ -114,16 +109,13 @@ public class ProfileManager implements ProfilingListener {
|
||||
* Collect all the profiling information.
|
||||
*/
|
||||
public AutoTuneCollection profilingCollection(boolean reset) {
|
||||
|
||||
AutoTuneCollection req = new AutoTuneCollection();
|
||||
|
||||
for (ProfileOrigin origin : profileMap.values()) {
|
||||
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
|
||||
if (desc != null) {
|
||||
origin.profilingCollection(desc, req, reset);
|
||||
}
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -27,7 +27,7 @@ public class ProfileOriginTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail detail = po.buildDetail(desc);
|
||||
|
||||
assertThat(detail.asStringDebug().trim()).isEqualTo("fetch customer (name)");
|
||||
assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -46,7 +46,7 @@ public class ProfileOriginTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail detail = po.buildDetail(desc);
|
||||
|
||||
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (name)");
|
||||
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +64,7 @@ public class ProfileOriginTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail detail = po.buildDetail(desc);
|
||||
|
||||
assertThat(detail.asStringDebug().trim()).isEqualTo("select (orderDate,customer)");
|
||||
assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,7 +87,7 @@ public class ProfileOriginTest extends BaseTestCase {
|
||||
|
||||
OrmQueryDetail detail = po.buildDetail(desc);
|
||||
|
||||
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
|
||||
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ public class ProfileOriginTest extends BaseTestCase {
|
||||
po.collectUsageInfo(c);
|
||||
|
||||
OrmQueryDetail detail = po.buildDetail(desc);
|
||||
assertThat(detail.asStringDebug()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
|
||||
assertThat(detail.asString()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
|
||||
}
|
||||
|
||||
private NodeUsageCollector node(String path) {
|
||||
|
||||
+17
-17
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -18,8 +18,8 @@
|
||||
<ebean-migration.version>12.4.0</ebean-migration.version>
|
||||
<ebean-test-docker.version>4.1</ebean-test-docker.version>
|
||||
<ebean-datasource.version>7.0</ebean-datasource.version>
|
||||
<ebean-agent.version>12.6.6</ebean-agent.version>
|
||||
<ebean-maven-plugin.version>12.6.6</ebean-maven-plugin.version>
|
||||
<ebean-agent.version>12.9.0</ebean-agent.version>
|
||||
<ebean-maven-plugin.version>12.9.1</ebean-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -81,88 +81,88 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-xml</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-autotune</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+17
-17
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.8.0</tag>
|
||||
<tag>ebean-parent-12.9.1</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -72,7 +72,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.1</version>
|
||||
<version>12.9.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -87,19 +87,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.8.0</version>
|
||||
<version>12.9.1</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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public interface LoadContext {
|
||||
/**
|
||||
* Return the minimum batch size when using QueryIterator with query joins.
|
||||
*/
|
||||
int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
|
||||
int getSecondaryQueriesMinBatchSize();
|
||||
|
||||
/**
|
||||
* Execute any secondary (+query) queries if there are any defined.
|
||||
|
||||
@@ -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) {
|
||||
@@ -128,7 +123,7 @@ public class LoadManyRequest extends LoadRequest {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -147,6 +147,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>.
|
||||
|
||||
@@ -43,6 +43,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();
|
||||
|
||||
@@ -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,26 +1357,29 @@ 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;
|
||||
@@ -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,11 +1424,13 @@ 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);
|
||||
@@ -1470,6 +1497,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 +1509,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 +1569,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);
|
||||
@@ -2241,6 +2271,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 +2283,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 +2350,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
|
||||
if (initRequest.isAll()) {
|
||||
queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros());
|
||||
}
|
||||
return beanDescriptorManager.queryPlanInit(initRequest);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,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;
|
||||
@@ -133,9 +136,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -173,8 +173,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
* iteration is fine.
|
||||
* </p>
|
||||
*/
|
||||
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
|
||||
return loadContext.getSecondaryQueriesMinBatchSize(defaultQueryBatch);
|
||||
public int getSecondaryQueriesMinBatchSize() {
|
||||
return loadContext.getSecondaryQueriesMinBatchSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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() {
|
||||
|
||||
@@ -887,6 +887,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 +1038,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() {
|
||||
|
||||
@@ -2473,7 +2473,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 +2489,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3134,7 +3134,7 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
|
||||
|
||||
private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
|
||||
public static final String JAVA_LANG_RECORD = "java.lang.Record";
|
||||
|
||||
private final ReadAnnotations readAnnotations;
|
||||
private final TransientProperties transientProperties;
|
||||
@@ -1418,14 +1419,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
*/
|
||||
private void checkInheritedClasses(Class<?> beanClass) {
|
||||
Class<?> superclass = beanClass.getSuperclass();
|
||||
if (Object.class.equals(superclass)) {
|
||||
if (Object.class.equals(superclass) || Model.class.equals(superclass) || JAVA_LANG_RECORD.equals(superclass.getName())) {
|
||||
// we got to the top of the inheritance
|
||||
return;
|
||||
}
|
||||
if (Model.class.equals(superclass)) {
|
||||
// top of the inheritance. Not enhancing Model at this stage
|
||||
return;
|
||||
}
|
||||
if (!EntityBean.class.isAssignableFrom(superclass)) {
|
||||
if (isMappedSuperWithNoProperties(superclass)) {
|
||||
// ok to stop and treat just the same as Object.class
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-9
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+8
-2
@@ -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;
|
||||
}
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
@@ -33,7 +33,7 @@ abstract class DLoadBaseContext {
|
||||
|
||||
final boolean queryFetch;
|
||||
|
||||
DLoadBaseContext(DLoadContext parent, BeanDescriptor<?> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
|
||||
DLoadBaseContext(DLoadContext parent, BeanDescriptor<?> desc, String path, OrmQueryProperties queryProps) {
|
||||
this.parent = parent;
|
||||
this.serverName = parent.getEbeanServer().getName();
|
||||
this.desc = desc;
|
||||
@@ -42,11 +42,7 @@ abstract class DLoadBaseContext {
|
||||
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
|
||||
this.objectGraphNode = parent.getObjectGraphNode(path);
|
||||
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
|
||||
this.batchSize = initBatchSize(defaultBatchSize, queryProps);
|
||||
}
|
||||
|
||||
private int initBatchSize(int batchSize, OrmQueryProperties queryProps) {
|
||||
return queryProps == null ? batchSize : queryProps.getBatchSize();
|
||||
this.batchSize = parent.batchSize(queryProps);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,8 +31,8 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
|
||||
|
||||
private LoadBuffer currentBuffer;
|
||||
|
||||
DLoadBeanContext(DLoadContext parent, BeanDescriptor<?> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
|
||||
super(parent, desc, path, defaultBatchSize, queryProps);
|
||||
DLoadBeanContext(DLoadContext parent, BeanDescriptor<?> desc, String path, OrmQueryProperties queryProps) {
|
||||
super(parent, desc, path, queryProps);
|
||||
// bufferList only required when using query joins (queryFetch)
|
||||
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
|
||||
this.currentBuffer = createBuffer(batchSize);
|
||||
|
||||
@@ -90,7 +90,7 @@ public class DLoadContext implements LoadContext {
|
||||
this.planLabel = null;
|
||||
this.profileLocation = null;
|
||||
this.profilingListener = null;
|
||||
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
|
||||
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
|
||||
}
|
||||
|
||||
private ObjectGraphOrigin initOrigin() {
|
||||
@@ -128,7 +128,7 @@ public class DLoadContext implements LoadContext {
|
||||
}
|
||||
|
||||
// initialise rootBeanContext after origin and relativePath have been set
|
||||
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
|
||||
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
|
||||
registerSecondaryQueries(secondaryQueries);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public class DLoadContext implements LoadContext {
|
||||
* Return the minimum batch size when using QueryIterator with query joins.
|
||||
*/
|
||||
@Override
|
||||
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
|
||||
public int getSecondaryQueriesMinBatchSize() {
|
||||
if (secQuery == null) {
|
||||
return -1;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ public class DLoadContext implements LoadContext {
|
||||
for (OrmQueryProperties aSecQuery : secQuery) {
|
||||
int batchSize = aSecQuery.getBatchSize();
|
||||
if (batchSize == 0) {
|
||||
batchSize = defaultQueryBatch;
|
||||
batchSize = 100;
|
||||
}
|
||||
maxBatch = Math.max(maxBatch, batchSize);
|
||||
}
|
||||
@@ -287,51 +287,55 @@ public class DLoadContext implements LoadContext {
|
||||
getManyContext(path, many).register(bc);
|
||||
}
|
||||
|
||||
int batchSize(OrmQueryProperties props) {
|
||||
if (props == null) {
|
||||
return defaultBatchSize;
|
||||
}
|
||||
int batchSize = props.getBatchSize();
|
||||
return batchSize == 0 ? defaultBatchSize : batchSize;
|
||||
}
|
||||
|
||||
DLoadBeanContext getBeanContext(String path) {
|
||||
if (path == null) {
|
||||
return rootBeanContext;
|
||||
}
|
||||
return beanMap.computeIfAbsent(path, p -> createBeanContext(p, defaultBatchSize, null));
|
||||
return beanMap.computeIfAbsent(path, p -> createBeanContext(p, null));
|
||||
}
|
||||
|
||||
DLoadBeanContext getBeanContextWithInherit(String path, BeanPropertyAssocOne<?> property) {
|
||||
String key = path + ":" + property.getTargetDescriptor().getName();
|
||||
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, defaultBatchSize, null));
|
||||
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, null));
|
||||
}
|
||||
|
||||
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
|
||||
int batchSize = props.getBatchSize();
|
||||
if (batchSize == 0) {
|
||||
batchSize = defaultBatchSize;
|
||||
}
|
||||
String path = props.getPath();
|
||||
if (many) {
|
||||
manyMap.put(path, createManyContext(path, batchSize, props));
|
||||
manyMap.put(path, createManyContext(path, props));
|
||||
} else {
|
||||
beanMap.put(path, createBeanContext(path, batchSize, props));
|
||||
beanMap.put(path, createBeanContext(path, props));
|
||||
}
|
||||
}
|
||||
|
||||
DLoadManyContext getManyContext(String path, BeanPropertyAssocMany<?> many) {
|
||||
return manyMap.computeIfAbsent(path, p -> createManyContext(p, many, defaultBatchSize));
|
||||
return manyMap.computeIfAbsent(path, p -> createManyContext(p, many));
|
||||
}
|
||||
|
||||
private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany<?> many, int batchSize) {
|
||||
return new DLoadManyContext(this, many, path, batchSize, null);
|
||||
private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany<?> many) {
|
||||
return new DLoadManyContext(this, many, path, null);
|
||||
}
|
||||
|
||||
private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) {
|
||||
private DLoadManyContext createManyContext(String path, OrmQueryProperties queryProps) {
|
||||
BeanPropertyAssocMany<?> p = (BeanPropertyAssocMany<?>) getBeanProperty(rootDescriptor, path);
|
||||
return new DLoadManyContext(this, p, path, batchSize, queryProps);
|
||||
return new DLoadManyContext(this, p, path, queryProps);
|
||||
}
|
||||
|
||||
private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) {
|
||||
private DLoadBeanContext createBeanContext(String path, OrmQueryProperties queryProps) {
|
||||
BeanPropertyAssoc<?> p = (BeanPropertyAssoc<?>) getBeanProperty(rootDescriptor, path);
|
||||
return new DLoadBeanContext(this, p.getTargetDescriptor(), path, batchSize, queryProps);
|
||||
return new DLoadBeanContext(this, p.getTargetDescriptor(), path, queryProps);
|
||||
}
|
||||
|
||||
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path, int batchSize, OrmQueryProperties queryProps) {
|
||||
return new DLoadBeanContext(this, property.getTargetDescriptor(), path, batchSize, queryProps);
|
||||
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path, OrmQueryProperties queryProps) {
|
||||
return new DLoadBeanContext(this, property.getTargetDescriptor(), path, queryProps);
|
||||
}
|
||||
|
||||
private BeanProperty getBeanProperty(BeanDescriptor<?> desc, String path) {
|
||||
|
||||
@@ -31,11 +31,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
|
||||
|
||||
private LoadBuffer currentBuffer;
|
||||
|
||||
DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany<?> property,
|
||||
String path, int defaultBatchSize, OrmQueryProperties queryProps) {
|
||||
|
||||
super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
|
||||
|
||||
DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany<?> property, String path, OrmQueryProperties queryProps) {
|
||||
super(parent, property.getBeanDescriptor(), path, queryProps);
|
||||
this.property = property;
|
||||
this.docStoreMapped = property.isTargetDocStoreMapped();
|
||||
// bufferList only required when using query joins (queryFetch)
|
||||
@@ -204,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();
|
||||
@@ -218,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;
|
||||
}
|
||||
}
|
||||
@@ -225,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,8 +37,6 @@ public class CQueryEngine {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class);
|
||||
|
||||
private static final int defaultSecondaryQueryBatchSize = 100;
|
||||
|
||||
private static final String T0 = "t0";
|
||||
|
||||
private final int defaultFetchSizeFindList;
|
||||
@@ -214,7 +212,7 @@ public class CQueryEngine {
|
||||
logSql(cquery);
|
||||
}
|
||||
// first check batch sizes set on query joins
|
||||
int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);
|
||||
int iterateBufferSize = request.getSecondaryQueriesMinBatchSize();
|
||||
if (iterateBufferSize < 1) {
|
||||
// not set on query joins so check if batch size set on query itself
|
||||
int queryBatch = request.getQuery().getLazyLoadBatchSize();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -740,10 +742,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;
|
||||
@@ -1232,6 +1238,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 +1419,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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+27
-4
@@ -1,11 +1,9 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -200,6 +198,20 @@ public final class DefaultPersistenceContext implements PersistenceContext {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> dirtyBeans() {
|
||||
lock.lock();
|
||||
try {
|
||||
List<Object> list = new ArrayList<>();
|
||||
for (ClassContext classContext : typeCache.values()) {
|
||||
classContext.dirtyBeans(list);
|
||||
}
|
||||
return list;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
lock.lock();
|
||||
@@ -318,6 +330,17 @@ public final class DefaultPersistenceContext implements PersistenceContext {
|
||||
deleteSet.add(id);
|
||||
map.remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dirty beans to the list.
|
||||
*/
|
||||
void dirtyBeans(List<Object> list) {
|
||||
for (Object value : map.values()) {
|
||||
if (((EntityBean) value)._ebean_getIntercept().isDirty()) {
|
||||
list.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -95,6 +95,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
|
||||
|
||||
@@ -187,6 +187,8 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
|
||||
private final long startNanos;
|
||||
|
||||
private boolean autoPersistUpdates;
|
||||
|
||||
/**
|
||||
* Create a new JdbcTransaction.
|
||||
*/
|
||||
@@ -209,11 +211,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 +297,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 +788,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) {
|
||||
@@ -1042,7 +1060,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
try {
|
||||
if (queryOnly) {
|
||||
if (queryOnly && !autoPersistUpdates) {
|
||||
connectionEndForQueryOnly();
|
||||
} else {
|
||||
flushCommitAndNotify();
|
||||
|
||||
@@ -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
|
||||
|
||||
+24
-34
@@ -5,6 +5,7 @@ import io.ebean.ProfileLocation;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.TxType;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.cache.ServerCacheNotification;
|
||||
import io.ebean.cache.ServerCacheNotify;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
@@ -17,6 +18,7 @@ import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetricMap;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebeaninternal.api.ScopeTrans;
|
||||
import io.ebeaninternal.api.ScopedTransaction;
|
||||
import io.ebeaninternal.api.SpiLogManager;
|
||||
@@ -51,7 +53,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 +60,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 +103,8 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
*/
|
||||
final DocStoreUpdateProcessor docStoreUpdateProcessor;
|
||||
|
||||
private final boolean autoPersistUpdates;
|
||||
|
||||
private final boolean persistBatch;
|
||||
|
||||
private final boolean persistBatchOnCascade;
|
||||
@@ -153,7 +158,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 +166,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 +197,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 +283,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 +301,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 +352,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 +400,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 +408,6 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
}
|
||||
txnLogger.debug(msg);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error while notifying TransactionEventListener of rollback event", ex);
|
||||
}
|
||||
@@ -434,7 +434,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 +456,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 +474,6 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
}
|
||||
|
||||
private void externalModificationEvent(TransactionEventTable tableEvents) {
|
||||
|
||||
TransactionEvent event = new TransactionEvent();
|
||||
event.add(tableEvents);
|
||||
|
||||
@@ -490,25 +486,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 +509,6 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
persistIds.notifyCache(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
changeSet.apply();
|
||||
}
|
||||
|
||||
@@ -532,10 +523,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 +631,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 +654,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 +723,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 +766,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(PersistenceContext persistenceContext, SpiTransaction transaction) {
|
||||
List<Object> dirtyBeans = persistenceContext.dirtyBeans();
|
||||
if (!dirtyBeans.isEmpty()) {
|
||||
server.updateAll(dirtyBeans, transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -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;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ebean-version: 129
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.FetchConfig;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -10,7 +9,7 @@ public class FetchConfigTest {
|
||||
@Test
|
||||
public void testLazy() {
|
||||
FetchConfig config = new FetchConfig().lazy();
|
||||
assertThat(config.getBatchSize()).isEqualTo(10);
|
||||
assertThat(config.getBatchSize()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -2,18 +2,21 @@ package io.ebean.common;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.EBasic;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
|
||||
public class BeanMapTest {
|
||||
|
||||
Object object1 = new Object();
|
||||
Object object2 = new Object();
|
||||
Object object3 = new Object();
|
||||
private final EBasic object1 = new EBasic("o1");
|
||||
private final EBasic object2 = new EBasic("o2");
|
||||
private final EBasic object3 = new EBasic("o3");
|
||||
private final EBasic object4 = new EBasic("o4");
|
||||
private final EBasic object5 = new EBasic("o5");
|
||||
|
||||
private Map<String, Object> all() {
|
||||
Map<String, Object> all = new LinkedHashMap<>();
|
||||
@@ -174,9 +177,7 @@ public class BeanMapTest {
|
||||
@Test
|
||||
public void testClear_given_someBeansInAdditions() throws Exception {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
BeanMap<String, EBasic> map = newModifyListeningMap();
|
||||
map.put("2", object2);
|
||||
map.put("3", object3);
|
||||
|
||||
@@ -188,4 +189,252 @@ public class BeanMapTest {
|
||||
assertThat(map.getModifyAdditions()).isEmpty();
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void keySet_add_whenModifyListening() {
|
||||
BeanMap<String, EBasic> map = newModifyListeningMap();
|
||||
map.keySet().add("3");
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void keySet_add() {
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.keySet().add("3");
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void keySet_addAll_whenModifyListening() {
|
||||
BeanMap<String, EBasic> map = newModifyListeningMap();
|
||||
map.keySet().addAll(asList("3", "4"));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void keySet_addAll() {
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.keySet().addAll(asList("3", "4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keySet_remove() {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
|
||||
final Set<String> keySet = map.keySet();
|
||||
keySet.remove("1");
|
||||
|
||||
assertThat(keySet.contains("1")).isFalse();
|
||||
assertThat(map).doesNotContainKeys("1");
|
||||
assertThat(map.get("1")).isNull();
|
||||
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keySet_clear() {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
|
||||
final Set<String> keySet = map.keySet();
|
||||
keySet.clear();
|
||||
|
||||
assertThat(map).isEmpty();
|
||||
assertThat(keySet).isEmpty();
|
||||
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1, object2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keySet_iterator_remove() {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.put("3", object3);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
|
||||
final Set<String> keySet = map.keySet();
|
||||
final Iterator<String> iterator = keySet.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final String key = iterator.next();
|
||||
if (key.equals("2")) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(keySet).hasSize(2);
|
||||
assertThat(keySet).containsExactly("1", "3");
|
||||
assertThat(map).containsKeys("1", "3");
|
||||
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keySet_removeAll() {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.put("3", object3);
|
||||
map.put("4", object4);
|
||||
map.put("5", object5);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
|
||||
final Set<String> keySet = map.keySet();
|
||||
final boolean changed = keySet.removeAll(asList("2", "3", "5"));
|
||||
|
||||
assertThat(changed).isTrue();
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(keySet).hasSize(2);
|
||||
assertThat(keySet).containsExactly("1", "4");
|
||||
assertThat(map).containsKeys("1", "4");
|
||||
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void keySet_retainAll() {
|
||||
|
||||
BeanMap<String, Object> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.put("3", object3);
|
||||
map.put("4", object4);
|
||||
map.put("5", object5);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
|
||||
final Set<String> keySet = map.keySet();
|
||||
final boolean changed = keySet.retainAll(asList("2", "3", "5"));
|
||||
|
||||
assertThat(changed).isTrue();
|
||||
assertThat(map).hasSize(3);
|
||||
assertThat(keySet).hasSize(3);
|
||||
assertThat(keySet).containsExactly("2", "3", "5");
|
||||
assertThat(map).containsKeys("2", "3", "5");
|
||||
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1, object4);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void values_add() {
|
||||
BeanMap<String, EBasic> map = new BeanMap<>();
|
||||
map.values().add(object3);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void values_addAll() {
|
||||
BeanMap<String, EBasic> map = new BeanMap<>();
|
||||
map.values().addAll(asList(object3, object5));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void entrySet_add() {
|
||||
newModifyListeningMap()
|
||||
.entrySet()
|
||||
.add(new AbstractMap.SimpleEntry<>("3", object3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_clear() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
entries.clear();
|
||||
|
||||
assertThat(entries).isEmpty();
|
||||
assertThat(map).isEmpty();
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_remove() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap5();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
|
||||
assertThat(map).hasSize(5);
|
||||
|
||||
final boolean existed1 = entries.remove(new AbstractMap.SimpleEntry<>("1", object1));
|
||||
assertThat(existed1).isTrue();
|
||||
|
||||
final boolean existed22 = entries.remove(new AbstractMap.SimpleEntry<>("22", object1));
|
||||
assertThat(existed22).isFalse();
|
||||
|
||||
assertThat(map).hasSize(4);
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_remove_whenNotEqualValue() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap5();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
|
||||
assertThat(map).hasSize(5);
|
||||
|
||||
final boolean modified = entries.remove(new AbstractMap.SimpleEntry<>("1", object2));
|
||||
assertThat(modified).isFalse();
|
||||
|
||||
assertThat(map).hasSize(5);
|
||||
assertThat(map.getModifyRemovals()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_iterator_remove() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap5();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
final Iterator<Map.Entry<String, EBasic>> iterator = entries.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final Map.Entry<String, EBasic> entry = iterator.next();
|
||||
if (entry.getKey().equals("2") || entry.getKey().equals("5")) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
assertThat(map).hasSize(3);
|
||||
assertThat(entries).hasSize(3);
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object2, object5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_removeAll() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap5();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
|
||||
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
|
||||
assertThat(map).hasSize(3);
|
||||
assertThat(entries).hasSize(3);
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object1, object4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void entrySet_retainAll() {
|
||||
final BeanMap<String, EBasic> map = newModifyListeningMap5();
|
||||
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
|
||||
|
||||
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
|
||||
assertThat(map).hasSize(2);
|
||||
assertThat(entries).hasSize(2);
|
||||
assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5);
|
||||
}
|
||||
|
||||
private BeanMap<String, EBasic> newModifyListeningMap() {
|
||||
BeanMap<String, EBasic> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
return map;
|
||||
}
|
||||
|
||||
private BeanMap<String, EBasic> newModifyListeningMap5() {
|
||||
BeanMap<String, EBasic> map = new BeanMap<>();
|
||||
map.put("1", object1);
|
||||
map.put("2", object2);
|
||||
map.put("3", object3);
|
||||
map.put("4", object4);
|
||||
map.put("5", object5);
|
||||
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +72,16 @@ public class ServerConfigTest {
|
||||
props.setProperty("enabledL2Regions", "r0,users,orgs");
|
||||
props.setProperty("caseSensitiveCollation", "false");
|
||||
props.setProperty("loadModuleInfo", "true");
|
||||
props.setProperty("collectQueryPlanThresholdMicros", "10000");
|
||||
props.setProperty("forUpdateNoKey", "true");
|
||||
props.setProperty("defaultServer", "false");
|
||||
|
||||
props.setProperty("queryPlan.enable", "true");
|
||||
props.setProperty("queryPlan.thresholdMicros", "10000");
|
||||
props.setProperty("queryPlan.capture", "true");
|
||||
props.setProperty("queryPlan.capturePeriodSecs", "42");
|
||||
props.setProperty("queryPlan.captureMaxTimeMillis", "560");
|
||||
props.setProperty("queryPlan.captureMaxCount", "7");
|
||||
|
||||
serverConfig.loadFromProperties(props);
|
||||
|
||||
assertFalse(serverConfig.isDefaultServer());
|
||||
@@ -97,7 +103,6 @@ public class ServerConfigTest {
|
||||
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
|
||||
assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime());
|
||||
assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate());
|
||||
assertEquals(10000, serverConfig.getCollectQueryPlanThresholdMicros());
|
||||
|
||||
assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions());
|
||||
|
||||
@@ -106,6 +111,13 @@ public class ServerConfigTest {
|
||||
assertEquals(4, serverConfig.getBackgroundExecutorSchedulePoolSize());
|
||||
assertEquals(98, serverConfig.getBackgroundExecutorShutdownSecs());
|
||||
|
||||
assertTrue(serverConfig.isQueryPlanEnable());
|
||||
assertEquals(10000, serverConfig.getQueryPlanThresholdMicros());
|
||||
assertTrue(serverConfig.isQueryPlanCapture());
|
||||
assertEquals(42, serverConfig.getQueryPlanCapturePeriodSecs());
|
||||
assertEquals(560, serverConfig.getQueryPlanCaptureMaxTimeMillis());
|
||||
assertEquals(7, serverConfig.getQueryPlanCaptureMaxCount());
|
||||
|
||||
assertThat(serverConfig.getMappingLocations()).containsExactly("classpath:/foo","bar");
|
||||
|
||||
serverConfig.setPersistBatch(PersistBatch.NONE);
|
||||
@@ -137,6 +149,7 @@ public class ServerConfigTest {
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
assertTrue(serverConfig.isIdGeneratorAutomatic());
|
||||
assertTrue(serverConfig.isDefaultServer());
|
||||
assertFalse(serverConfig.isAutoPersistUpdates());
|
||||
|
||||
serverConfig.setIdGeneratorAutomatic(false);
|
||||
assertFalse(serverConfig.isIdGeneratorAutomatic());
|
||||
@@ -144,10 +157,18 @@ public class ServerConfigTest {
|
||||
assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate());
|
||||
assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
|
||||
assertTrue(serverConfig.isAutoLoadModuleInfo());
|
||||
assertEquals(Long.MAX_VALUE, serverConfig.getCollectQueryPlanThresholdMicros());
|
||||
|
||||
assertFalse(serverConfig.isQueryPlanEnable());
|
||||
assertEquals(Long.MAX_VALUE, serverConfig.getQueryPlanThresholdMicros());
|
||||
assertFalse(serverConfig.isQueryPlanCapture());
|
||||
assertEquals(600, serverConfig.getQueryPlanCapturePeriodSecs());
|
||||
assertEquals(10000L, serverConfig.getQueryPlanCaptureMaxTimeMillis());
|
||||
assertEquals(10, serverConfig.getQueryPlanCaptureMaxCount());
|
||||
|
||||
serverConfig.setLoadModuleInfo(false);
|
||||
assertFalse(serverConfig.isAutoLoadModuleInfo());
|
||||
serverConfig.setAutoPersistUpdates(true);
|
||||
assertTrue(serverConfig.isAutoPersistUpdates());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+35
-1
@@ -288,7 +288,7 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_removeJoinToMany_when_filterMany() {
|
||||
public void test_filterMany_included() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class)
|
||||
.fetch("details")
|
||||
@@ -301,6 +301,40 @@ public class DefaultServer_createOrmQueryRequestTest extends BaseTestCase {
|
||||
OrmQueryRequest<Order> queryRequest = queryRequest(query);
|
||||
OrmQueryDetail detail = queryRequest.getQuery().getDetail();
|
||||
|
||||
assertThat(detail.getFetchPaths()).containsExactly("details", "details.product", "customer");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_filterMany_excludedByOrdering() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class)
|
||||
.fetch("customer")
|
||||
.fetch("customer.contacts")
|
||||
.fetch("details")
|
||||
.fetch("details.product")
|
||||
.filterMany("details").eq("orderQuantity", 10)
|
||||
.query();
|
||||
|
||||
OrmQueryRequest<Order> queryRequest = queryRequest(query);
|
||||
OrmQueryDetail detail = queryRequest.getQuery().getDetail();
|
||||
|
||||
assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_filterMany_excludedExplicitly() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class)
|
||||
.fetchQuery("details")
|
||||
.fetch("details.product")
|
||||
.fetch("customer")
|
||||
.fetch("customer.contacts")
|
||||
.filterMany("details").eq("orderQuantity", 10)
|
||||
.query();
|
||||
|
||||
OrmQueryRequest<Order> queryRequest = queryRequest(query);
|
||||
OrmQueryDetail detail = queryRequest.getQuery().getDetail();
|
||||
|
||||
assertThat(detail.getFetchPaths()).containsExactly("customer", "customer.contacts");
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ public class DtoQueryRequestTest {
|
||||
public void testParse() {
|
||||
|
||||
assertEquals("foo", DtoQueryRequest.parseColumn("foo"));
|
||||
assertEquals("bar", DtoQueryRequest.parseColumn("_e_t0_bar"));
|
||||
assertEquals("BAR", DtoQueryRequest.parseColumn("_E_T0_BAR"));
|
||||
assertEquals("baz", DtoQueryRequest.parseColumn("_e_t42_baz"));
|
||||
assertEquals("BAZ", DtoQueryRequest.parseColumn("_E_T42_BAZ"));
|
||||
assertEquals("bar", DtoQueryRequest.parseColumn("zx__t0_bar"));
|
||||
assertEquals("BAR", DtoQueryRequest.parseColumn("ZX__T0_BAR"));
|
||||
assertEquals("baz", DtoQueryRequest.parseColumn("zx__t42_baz"));
|
||||
assertEquals("BAZ", DtoQueryRequest.parseColumn("ZX__T42_BAZ"));
|
||||
|
||||
assertEquals("e_t42_nope", DtoQueryRequest.parseColumn("e_t42_nope"));
|
||||
assertEquals("_f_t42_nope", DtoQueryRequest.parseColumn("_f_t42_nope"));
|
||||
|
||||
@@ -4,21 +4,22 @@ import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployIdentityMode;
|
||||
import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Animal;
|
||||
import org.tests.model.basic.AnimalShelter;
|
||||
import org.tests.model.basic.Cat;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Country;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.Dog;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.tests.model.basic.*;
|
||||
import org.tests.model.bridge.BSite;
|
||||
import org.tests.model.bridge.BUser;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class BeanDescriptorTest extends BaseTestCase {
|
||||
|
||||
@@ -88,6 +89,42 @@ public class BeanDescriptorTest extends BaseTestCase {
|
||||
assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchBaseTable() {
|
||||
BeanDescriptor<Customer> desc = getBeanDescriptor(Customer.class);
|
||||
assertTrue(desc.matchBaseTable("o_customer"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchBaseTable_whenTableHasSchema_expect_matchRegardlessOfSchema() {
|
||||
|
||||
DeployBeanDescriptor<Customer> deploy = mockDeployCustomer();
|
||||
|
||||
when(deploy.getBaseTable()).thenReturn("foo.o_customer");
|
||||
BeanDescriptor<?> desc1 = new BeanDescriptor<>(mockOwner(), deploy);
|
||||
assertTrue(desc1.matchBaseTable("o_customer"));
|
||||
|
||||
when(deploy.getBaseTable()).thenReturn("bar.o_customer");
|
||||
BeanDescriptor<?> desc2 = new BeanDescriptor<>(mockOwner(), deploy);
|
||||
assertTrue(desc2.matchBaseTable("o_customer"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private DeployBeanDescriptor<Customer> mockDeployCustomer() {
|
||||
DeployBeanDescriptor<Customer> deploy = mock(DeployBeanDescriptor.class);
|
||||
when(deploy.getBeanType()).thenReturn(Customer.class);
|
||||
when(deploy.getIdentityMode()).thenReturn(DeployIdentityMode.auto());
|
||||
when(deploy.buildIdentityMode()).thenReturn(IdentityMode.NONE);
|
||||
when(deploy.getCacheOptions()).thenReturn(CacheOptions.NO_CACHING);
|
||||
return deploy;
|
||||
}
|
||||
|
||||
private BeanDescriptorMap mockOwner() {
|
||||
BeanDescriptorMap owner = mock(BeanDescriptorMap.class);
|
||||
when(owner.createDocStoreBeanAdapter(any(), any())).thenReturn(mock(DocStoreBeanAdapter.class));
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void merge_when_empty() {
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ public class ParseFetchConfigTest {
|
||||
@Test
|
||||
public void parseLazy() {
|
||||
FetchConfig lazy = ParseFetchConfig.parse("lazy");
|
||||
assertThat(lazy.getBatchSize()).isEqualTo(10);
|
||||
assertThat(lazy.getBatchSize()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -46,7 +46,12 @@ public class MainMemoryLeak {
|
||||
config.addClass(ECachedBean.class);
|
||||
config.loadFromProperties();
|
||||
|
||||
Database server1 = DatabaseFactory.create(config);
|
||||
assert server1 != null;
|
||||
|
||||
// create again for #2193
|
||||
Database server = DatabaseFactory.create(config);
|
||||
assert server != null;
|
||||
|
||||
// create a string with 10k chars
|
||||
char[] c = new char[10_000];
|
||||
|
||||
@@ -25,6 +25,10 @@ public class PersonCacheEmail {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public PersonCacheEmail(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.tests.cache.personinfo;
|
||||
|
||||
import io.ebean.annotation.WhenCreated;
|
||||
import io.ebean.annotation.WhenModified;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Version;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
public class PersonOther {
|
||||
|
||||
@Id
|
||||
@Size(max=128)
|
||||
private String id;
|
||||
|
||||
private String email;
|
||||
|
||||
@WhenCreated
|
||||
private Instant whenCreated;
|
||||
|
||||
@WhenModified
|
||||
private Instant whenModified;
|
||||
|
||||
@Version
|
||||
private long version;
|
||||
|
||||
public PersonOther(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public Instant getWhenCreated() {
|
||||
return whenCreated;
|
||||
}
|
||||
|
||||
public void setWhenCreated(Instant whenCreated) {
|
||||
this.whenCreated = whenCreated;
|
||||
}
|
||||
|
||||
public Instant getWhenModified() {
|
||||
return whenModified;
|
||||
}
|
||||
|
||||
public void setWhenModified(Instant whenModified) {
|
||||
this.whenModified = whenModified;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.tests.cache.personinfo;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestStringIdOnly extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void insert() {
|
||||
|
||||
PersonCacheEmail b0 = new PersonCacheEmail("IdOnly");
|
||||
DB.save(b0);
|
||||
|
||||
PersonCacheEmail found = DB.find(PersonCacheEmail.class, b0.getId());
|
||||
assertThat(found).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insert_whenIdOnly() {
|
||||
|
||||
PersonOther b0 = new PersonOther("IdOnly");
|
||||
DB.save(b0);
|
||||
|
||||
PersonOther found = DB.find(PersonOther.class, b0.getId());
|
||||
assertThat(found).isNotNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.tests.lazyloadconf;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
@Cache
|
||||
@Table(name = "app_config")
|
||||
public class AppConfig {
|
||||
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
private Integer id;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "appConfig")
|
||||
//@JoinColumn(name = "id", referencedColumnName = "id")
|
||||
private List<AppConfigControl> items;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<AppConfigControl> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<AppConfigControl> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user