Compare commits

...
Author SHA1 Message Date
rbygrave 64405ade52 [maven-release-plugin] prepare release ebean-parent-12.9.1 2021-06-04 17:59:11 +12:00
rbygrave ce5f1b25f5 Bump ebean-maven-plugin to 12.9.1 2021-06-04 17:50:46 +12:00
rbygrave ae4f50e7b3 #2236 - Auto-tune does not work / throws RuntimeException (Regression from 12.7.1)
Note we are missing a test on ProfileManager but doing this for now.
2021-06-04 17:46:31 +12:00
rbygrave c9120be347 No effective change - tidy only H2HistoryTrigger and H2HistoryDdl 2021-06-04 17:37:37 +12:00
rbygrave b189e67b4c #2241 - Offline DDL migration generation for H2 requires H2 Driver 2021-06-04 17:35:19 +12:00
Rob BygraveandGitHub 5d5c275c79 Merge pull request #2240 from FOCONIS/bugfix/ddl-generator-version
Set ddl-generator version to a public-available version + fix classpath for Java8
2021-06-04 17:24:31 +12:00
rbygrave 52f31a7a8e Merge branch 'bkempe-testSelectOneToOneDisjunction' 2021-06-04 17:17:21 +12:00
rbygrave 6697aecd48 #2238 - Select with disjunction of expressions over OneToOne relationship missing join clause
Noting that there might be a better fix. The addJoin() at line 151 is suppressed as it is a duplicate for this case of a !oneToOneExported so a better fix might be to only add the soft delete clause when the join as actually added (not suppressed as a duplicate).
2021-06-04 17:16:50 +12:00
Jonas Pöhler 348c075a14 FIX: Build for java8 2021-06-01 17:56:47 +02:00
Jonas Pöhler bf2420702c FIX: set ddl-generator version to a repo-available version 2021-06-01 17:19:38 +02:00
Ben Kempe 16832ac67a add test testSelectOneToOneDisjunction 2021-05-24 23:47:55 +02:00
rbygrave 47bd2ff03d [maven-release-plugin] prepare for next development iteration 2021-05-23 23:08:47 +12:00
rbygrave 850f1c1469 [maven-release-plugin] prepare release ebean-parent-12.9.0 2021-05-23 23:08:38 +12:00
rbygrave 622a90c7d5 No effective change - tidy whitespace in TransactionManager 2021-05-23 23:00:34 +12:00
rbygrave 4c317f11ae Add additional test for PrimaryKeyJoinColumn 2021-05-23 22:21:05 +12:00
rbygrave 01bce5cfa6 #2226 - Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name Id warning when I want to use Id as a foreign key
Improve the message and dropping to INFO level.
2021-05-23 22:20:40 +12:00
rbygrave 2158925d3c #2202 - kotlin-maven-plugin issue with JDK 16
InaccessibleObjectException: Unable to make protected void java.util.ResourceBundle.setParent(java.util.ResourceBundle) accessible: module java.base does not "opens java.util" to unnamed module @62732be7
2021-05-23 21:28:11 +12:00
Rob BygraveandGitHub 2aff6403a2 Merge pull request #2235 from ebean-orm/feature/2223
#2223 #2224 Support BeanMap modification via entrySet() and keySet()
2021-05-23 17:20:55 +12:00
rbygrave 3c4d600187 #2223 #2224 Support BeanMap modification via entrySet() and keySet() 2021-05-23 17:17:34 +12:00
Rob BygraveandGitHub f60ccb9b90 Merge pull request #2222 from ebean-orm/feature/support-filterMany-inline
Change such that filterMany can be included in the main query.
2021-05-21 17:20:17 +12:00
rbygrave 2a6108087c Bump version to 12.9.0-SNAPSHOT 2021-05-21 17:18:30 +12:00
rbygrave 0da86a82ae #2233 Bump to ebean-agent 12.9.0 2021-05-21 17:14:49 +12:00
rbygrave 643b0499c4 #2233 Add ebean-version.mf so ebean-agent can sniff the internal version
This allows the ebean-agent to support the older behaviour. Devs using the latest IntelliJ ebean plugin will get the old behaviour unless they bump ebean-core to 12.9 or later.
2021-05-21 16:09:15 +12:00
rbygrave 676bac390e #2233 Followup change that unsets load buffer from BeanCollection
- Clears the buffer after it has been used to load BeanCollection's
- Sets the BeanCollection to use the server rather than the Load Buffer just in case there are subsequent calls for loading (which there should not be)
2021-05-21 09:01:45 +12:00
rbygrave 79740accfa #2233 Fix with setter on ToMany now not invoking lazy loading
- ebean-agent changed to not invoke preGetter on ToMany (just like Id)
- EntityBeanIntercept preSetterMany() changed to ensure loading flag set
2021-05-21 08:48:42 +12:00
rbygrave 77c6f1cb9f #2233 Tidy failing test Ebean -> DB and rename test 2021-05-21 08:44:04 +12:00
rbygrave 538361d5fd #2233 Failing test 2021-05-21 08:33:31 +12:00
rbygrave b17593ba3c No effective change - tidy BeanList, BeanMap, BeanSet internals 2021-05-20 21:44:07 +12:00
rbygrave eb00690b30 #2231 - Change OrderBy to not be final to support mocking via Mockito 2021-05-20 14:09:22 +12:00
Rob BygraveandGitHub a772e65d32 Merge pull request #2225 from ebean-orm/dependabot/maven/ebean-core/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /ebean-core
2021-05-20 13:56:00 +12:00
Brian Payne 66f7f69ec7 Fixed bug with soft-delete on one-to-one exported 2021-05-12 09:24:33 -07:00
Brian Payne ad52711fe1 Add test demonstrating soft-delete bug with optional relationship 2021-05-11 10:42:04 -07:00
dependabot[bot]andGitHub d6fdb4ee34 Bump commons-io from 2.5 to 2.7 in /ebean-core
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 20:37:55 +00:00
rbygrave 74c7268d2f Change such that filterMany can be included in the main query.
That is, currently a filterMany automatically marks that path as a fetchQuery path. This change turns that off and the filterMany path and predicates can then be included in the main query.

This is better for performance (to include the filterMany path in with the main query) when the query is a findEach and looking to return a large number of results.
2021-04-21 18:57:03 +12:00
rbygrave 42fc419759 #2221 - ebean-redis: bump jedis dependency to 3.6.0 2021-04-20 22:23:33 +12:00
rbygrave 4fb3b9cddc Tidy use of StringHelper replace() in BeanProperty, BaseTableDdl 2021-04-20 22:13:45 +12:00
Robin Bygrave c60077174e Tidy use of StringHelper replace() in BeanProperty, BaseTableDdl 2021-04-20 15:14:20 +12:00
rbygrave 5e893dd2ad [maven-release-plugin] prepare for next development iteration 2021-04-19 21:40:16 +12:00
rbygrave 9eb54da2dc [maven-release-plugin] prepare release ebean-parent-12.8.3 2021-04-19 21:40:07 +12:00
rbygrave bb6ff866c7 #2220 - DDL generation of nullable columns expected with nullable Embedded containing non null properties 2021-04-19 21:37:05 +12:00
Robin Bygrave 2d719c5b37 #2218 - @Encrypted columns on Oracle. The identifier alias starts with an underscore (ORA-00911)
Change to use zx__ for the encryption column alias prefix rather than _e_
2021-04-19 20:08:38 +12:00
Robin Bygrave 9d54b264ad #2219 - Compile error with ZonedDateTime and query beans - type argument java.time.ZonedDateTime is not within bounds of type-variable D 2021-04-19 17:04:57 +12:00
rbygrave 65b282a056 [maven-release-plugin] prepare for next development iteration 2021-04-09 17:49:16 +12:00
rbygrave fc174c8988 [maven-release-plugin] prepare release ebean-parent-12.8.2 2021-04-09 17:49:07 +12:00
rbygrave 06802896ef #2213 Bump agent for @Transactional(autoPersistUpdates = TxOption.ON) 2021-04-09 16:17:58 +12:00
Robin Bygrave 7f13ac9e5e #2213 - Support autoPersistUpdates via TxScope 2021-04-08 23:15:49 +12:00
Rob BygraveandGitHub 4a1374d22d Merge pull request #2213 from ebean-orm/feature/transparentPersistence
Initial experimental support for autoPersistUpdates / "transparent persistence"
2021-04-08 23:03:05 +12:00
rbygrave 4ee8d647b7 No effective change - tidy test TestCustomerFinder 2021-04-07 23:28:53 +12:00
rbygrave 59793f3c01 Improve javadoc for Transaction#setGetGeneratedKeys
More clearly document the limitation that we can't update beans that don't have id values
2021-04-07 15:50:04 +12:00
rbygrave f7f383d8f3 No effective change - tidy tests TestRawSqlPositionedParams, TestRawSqlUnparsedQuery 2021-04-07 13:38:34 +12:00
rbygrave 562a3959b6 Merge branch 'master' of github.com:ebean-orm/ebean 2021-04-06 21:14:21 +12:00
rbygrave bb1275f4f9 #2217 - Inserting an entity bean is skipped with a String @Id property and no other properties set 2021-04-06 21:13:59 +12:00
Rob BygraveandGitHub 7fde6b9331 Merge pull request #2216 from ebean-orm/feature/2177-take2
#2177 - Fix where schema not provided by JDBC driver
2021-04-06 20:13:24 +12:00
rbygrave 3e97a4005a Change TestCustomerFinder drop query plan threshold for tests only 2021-04-06 20:09:09 +12:00
Rob BygraveandGitHub c271760230 Merge pull request #2215 from PromanSEW/patch-1
Fix copy/paste typo in javadoc for endOr()
2021-04-06 20:00:02 +12:00
rbygrave f7c1845f0c #2177 - Fix where schema not provided by JDBC driver
For @Table with schema, using findNative with a JDBC driver that does not provide the schema this fix matches by just using the table name.
2021-04-06 19:58:02 +12:00
Roman ParshikovandGitHub 9b04f20ed3 Fix copy/paste typo 2021-04-06 13:32:57 +08:00
Rob BygraveandGitHub 62b6b6293b Merge pull request #2214 from cit-consulting/fix_with_lock
Explicit locking method withLock() is unaccessible on query beans
2021-04-06 08:27:38 +12:00
Vladimir Konkov 6d8a1c9398 Explicit locking method withLock() is unaccessible on query beans 2021-04-05 21:45:48 +03:00
Robin Bygrave 303039735f Refactor rename transparentPersistence to autoPersistUpdates 2021-04-01 23:04:22 +13:00
Robin Bygrave 6bcf5c6881 Add DatabaseConfig.autoPersistUpdates for experimental "transparent persistence" 2021-04-01 22:57:34 +13:00
Robin Bygrave 0527f45f5a For experimental "transparent persistence" - Inserts added to PC
When inserting bean with transparent persistence the beans are registered with the persistence context. This is done to support the cases where the beans are inserted, a flush() occurs, the inserted bean is mutated and now dirty - we want that to be detected and get an update
2021-04-01 16:59:03 +13:00
Robin Bygrave ba397bbb0c For experimental "transparent persistence" - Delete removes from PC early
When delete bean executed with transparent persistence, this marks the bean as removed from the persistence context early.

This avoids a "dirty" deleted bean from being seen as a "dirty" bean in the persistence context at flush() time.
2021-04-01 16:04:18 +13:00
Robin Bygrave a617d58902 Initial experimental support for "transparent persistence"
In short, when this is turned on at flush() get any dirty beans held by the PersistenceContext and persist them (always update for these beans)
2021-04-01 14:47:54 +13:00
Robin Bygrave 7fc4a229ae Merge branch 'master' of https://github.com/ebean-orm/ebean 2021-04-01 12:23:00 +13:00
Robin Bygrave f53d31aa2e No functional change - final field for BatchControl 2021-04-01 12:22:45 +13:00
Rob BygraveandGitHub b889b6a6b0 Merge pull request #2212 from tobias-/bitwise_querybean_operators
Add bitwise operators to query beans
2021-04-01 09:38:00 +13:00
Tobias f9b55a06b8 Add bitwise operators to query beans 2021-03-31 12:53:06 +02:00
rob bygrave 09d772a5e5 [maven-release-plugin] prepare for next development iteration 2021-03-31 00:06:13 +13:00
rob bygrave f7035eb228 [maven-release-plugin] prepare release ebean-parent-12.8.1 2021-03-31 00:06:04 +13:00
Robin Bygrave 1bf09ddd38 #2211 - Improve javadoc for queryPlanTTLSeconds
Hopefully people understand it is unrelated. Unfortunately we now have some properties with similar names so maybe we need a better name for this sometime in the future.
2021-03-30 23:49:44 +13:00
Robin Bygrave a8bf27da55 #2211 - Improve query plan capture - Align config property names
collectQueryPlans -> queryPlanEnable
collectQueryPlanThresholdMicros -> queryPlanThresholdMicros

properties keys:
ebean.collectQueryPlans -> ebean.queryPlan.enable
ebean.collectQueryPlanThresholdMicros -> ebean.queryPlan.thresholdMicros

This then aligns all the queryPlan related properties together which is important in order to avoid confusion.
2021-03-30 23:44:30 +13:00
Robin Bygrave ee8bb5ef75 #2211 - Improve query plan capture - Add default mechanism to log captured query plans 2021-03-30 23:19:21 +13:00
Robin Bygrave bcfd9cab1d #2210 - Improve query plan capture - bump threshold multiplier to 1.5 2021-03-30 16:05:42 +13:00
Robin Bygrave 2a5fd7f3a9 #2210 - Improve query plan capture - ability to change default threshold micros 2021-03-30 16:02:27 +13:00
Robin Bygrave f8c37772d4 Tidy internals of DefaultServer with @Nonnull, <>, Collections.singleton(), remove unused queryBatchSize 2021-03-30 10:23:28 +13:00
Robin Bygrave d988dbe69d #2209 - Registering [default] as the default server but [default] is already registered as the default exception with hot-reload 2021-03-30 09:56:48 +13:00
rob bygrave 5cb9b9b237 #2208 - When using fetchCache() with a single id / findOne ... doesn't hit L2 cache
Plus #2207 ENH: Query beans add support for nested FetchGroup
2021-03-29 23:21:07 +13:00
rob bygrave 819d391495 Bump agent maven plugin to 12.8.1 2021-03-26 20:31:57 +13:00
rob bygrave 1f0ed629a7 Bump java8-oss to 3.1 with removal of --illegal-access=permit argLine 2021-03-26 20:31:03 +13:00
rob bygrave 91044d0a75 javadoc only - improve javadoc for DatabaseConfig.loadFromProperties() 2021-03-25 23:13:51 +13:00
rob bygrave 57736185d0 #2206 - Postgres DDL generation - for create index use "if not exists" clause 2021-03-25 23:13:15 +13:00
Robin Bygrave 8992980689 #2206 - Postgres DDL generation - for create index use "if not exists" clause 2021-03-25 16:45:44 +13:00
rob bygrave f341ff0167 #2205 - [oracle] Incorrect DDL generated for alter table add column 2021-03-23 23:58:28 +13:00
rob bygrave abfa841ed7 #2205 - [oracle] Incorrect DDL generated for alter table add column 2021-03-23 23:51:07 +13:00
Rob BygraveandGitHub e68adf53bc Merge pull request #2204 from ebean-orm/feature/Honor-setLazyLoadBatchSize
Honor setLazyLoadBatchSize() when using FetchConfig.ofLazy() of unspecified size
2021-03-23 23:25:08 +13:00
Robin Bygrave 562d175de1 With FetchConfig.ofLazy() honor query.setLazyLoadBatchSize()
Use 0 for default lazy batch such that it honors a value set via query.setLazyLoadBatchSize()
2021-03-23 23:17:07 +13:00
Robin Bygrave b5cef354ad Refactor simplify LoadContext.getSecondaryQueriesMinBatchSize()
No functional change here. Just move the constant into DLoadContext.
2021-03-23 14:53:30 +13:00
Rob Bygrave 09a887aeb7 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for IdClass use of record type
2021-03-23 00:19:52 +13:00
Rob Bygrave 53c903fa48 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for EmbeddedId use of record type
2021-03-23 00:10:28 +13:00
Rob Bygrave 548454c90d #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for embedded use of record type
2021-03-22 23:57:14 +13:00
Rob Bygrave 94bab5f83b #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for entity use of record type
2021-03-22 23:44:10 +13:00
Rob Bygrave 78965c1ac4 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Allow java.lang.Record to be a root level parent for entity beans etc
2021-03-22 23:43:35 +13:00
Rob Bygrave 00b0fcaeb3 #2202 - kotlin-maven-plugin issue with JDK 16
Temporary modify tests to not invoke the kotlin-maven-plugin while adding JDK 16 specific tests.

Hitting error InaccessibleObjectException: Unable to make protected void java.util.ResourceBundle.setParent(java.util.ResourceBundle) accessible: module java.base does not "opens java.util" to unnamed module @62732be7
2021-03-22 23:24:16 +13:00
rob bygrave f7905c8e94 [maven-release-plugin] prepare for next development iteration 2021-03-18 22:30:17 +13:00
177 changed files with 3863 additions and 666 deletions
+2 -2
View File
@@ -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.
*/
+4 -11
View File
@@ -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;
+3 -3
View File
@@ -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>
@@ -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;
}
@@ -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
View File
@@ -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>
+2 -2
View File
@@ -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
View File
@@ -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
@@ -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
}
}
@@ -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);
}
@@ -44,9 +44,11 @@ public abstract class BaseTablePropertyVisitor implements BeanPropertyVisitor {
public abstract void visitOneImported(BeanPropertyAssocOne<?> p);
/**
* Override this method for normal scalar property.
* Override this method for normal scalar property. With allowNonNull
* used with properties of Embedded beans that can only be Nonnull when
* the Embedded bean is Nonnull.
*/
@Override
public abstract void visitScalar(BeanProperty p);
public abstract void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -40,8 +40,8 @@ public interface BeanPropertyVisitor {
void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded);
/**
* Visit a scalar property.
* Visit a scalar property specify allowing non-null (e.g. embedded bean scalar properties).
*/
void visitScalar(BeanProperty p);
void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -72,7 +72,7 @@ public class VisitProperties {
} else {
// simple scalar type
pv.visitScalar(p);
pv.visitScalar(p, true);
}
}
@@ -16,12 +16,17 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
public abstract class AbstractExpression implements SpiExpression {
protected final String propName;
protected String propName;
protected AbstractExpression(String propName) {
this.propName = propName;
}
@Override
public void prefixProperty(String path) {
this.propName = path + "." + propName;
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// by default can't use naturalKey cache
@@ -17,8 +17,8 @@ class BetweenPropertyExpression extends NonPrepareExpression {
private static final String BETWEEN = " between ";
private final String lowProperty;
private final String highProperty;
private String lowProperty;
private String highProperty;
private final Object value;
BetweenPropertyExpression(String lowProperty, String highProperty, Object value) {
@@ -27,6 +27,12 @@ class BetweenPropertyExpression extends NonPrepareExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
this.lowProperty = path + "." + lowProperty;
this.highProperty = path + "." + highProperty;
}
protected String name(String propName) {
return propName;
}
@@ -88,6 +88,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -126,6 +126,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public Junction<T> toJunction() {
return new JunctionExpression<>(Junction.Type.FILTER, this);
@@ -36,6 +36,11 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
this.subQuery = null;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -19,6 +19,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
throw new IllegalStateException("Not allowed?");
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeId(value);
@@ -66,6 +66,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
this.exprList = exprList;
}
@Override
public void prefixProperty(String path) {
exprList.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -57,6 +57,12 @@ abstract class LogicExpression implements SpiExpression {
this.expTwo = (SpiExpression) expTwo;
}
@Override
public void prefixProperty(String path) {
expOne.prefixProperty(path);
expTwo.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -24,6 +24,11 @@ class NestedPathWrapperExpression implements SpiExpression {
this.delegate = delegate;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -9,6 +9,11 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
abstract class NonPrepareExpression implements SpiExpression {
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -17,6 +17,11 @@ class NoopExpression implements SpiExpression {
protected static final NoopExpression INSTANCE = new NoopExpression();
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -22,6 +22,11 @@ final class NotExpression implements SpiExpression {
this.exp = (SpiExpression) exp;
}
@Override
public void prefixProperty(String path) {
exp.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -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;
}
/**
@@ -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);
}
}
}
}
}
@@ -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
@@ -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);
}
}
}
@@ -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
@@ -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