Compare commits

..
Author SHA1 Message Date
Rob Bygrave 65ba231fc0 [maven-release-plugin] prepare release ebean-11.1.1 2017-10-03 23:20:35 +13:00
Rob Bygrave 1da2382e45 No effective change - javadoc only 2017-10-03 15:38:17 +13:00
Rob Bygrave a86af94a8c Reduce logging level only - no functional change 2017-10-03 15:35:21 +13:00
Rob Bygrave 8301cb634e #1027 - ENH: Add a Slow query event listener ... to log or listen to queries that exceed some defined execution time 2017-10-03 15:04:30 +13:00
Rob Bygrave 721654d033 #1140 - Error loading embedded instance
The issue was due to the loaded status not being recursively set on embedded
beans after inserts. The test case invoked lazy loading due to that which
lead to the issue.
2017-10-03 12:47:29 +13:00
Rob Bygrave fe1817ef02 No effective change - update test 2017-10-03 12:34:56 +13:00
Rob Bygrave fc996659f2 #1139 - NoSuchMethodError: javax.persistence.Embedded.prefix()Ljava/lang/String; 2017-10-03 11:02:38 +13:00
rob bygrave 368d22baa6 #1145 - Refactor @ChangeLog based on JSON documents for 'new values' and 'old values' 2017-10-03 01:31:37 +13:00
rob bygrave daf3d97266 #1144 - Refactor internals - move generated property setting into PersistRequestBean 2017-10-03 00:30:34 +13:00
rob bygrave f2179fca2d #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:53 +12:00
rob bygrave 2c436c3af5 #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:37 +12:00
Roland PramlandRob Bygrave 8d334ff2e8 Feature/contitional test runner (#1132)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables

* ADD: ContitonaltestRunner to skip tests for certain platforms

* Use annotations to control on which platforms a test shoud run

* FIX: compile errors
2017-09-15 21:03:03 +12:00
rob bygrave f3e4696498 [maven-release-plugin] prepare for next development iteration 2017-09-14 01:03:21 +12:00
rob bygrave 221260fa44 [maven-release-plugin] prepare release ebean-10.4.7 2017-09-14 01:03:07 +12:00
rob bygrave f8e7a79e9a #1131 - Migration broken in 10.4.6 due different classpath-scanner version 2017-09-14 00:56:37 +12:00
Roland PramlandRob Bygrave b66f18bfd6 Fix warnings 2 (#1130)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables
2017-09-14 00:32:48 +12:00
Roland PramlandRob Bygrave ac6708e0d0 Fix warnings (#1129)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes
2017-09-14 00:29:01 +12:00
rob bygrave a5f46c095d [maven-release-plugin] prepare for next development iteration 2017-09-13 23:02:39 +12:00
rob bygrave 3ac7ea6e60 [maven-release-plugin] prepare release ebean-10.4.6 2017-09-13 23:02:25 +12:00
rob bygrave b2e0271ce7 #1128 - Allow "2 word SQL" like TRUNCATE t, ANALYSE t etc 2017-09-13 21:40:17 +12:00
rob bygrave c7a7651602 #1127 - Remove the ebean.query.globallimit of 1 million "backstop" 2017-09-13 21:22:03 +12:00
rob bygrave 1e6678cbb4 #1126 - Regression bug in 10.4.5 with new line character use with SqlQuery, SqlUpdate and CallableSql 2017-09-13 21:14:19 +12:00
Roland PramlandRob Bygrave a6e4e181bf Feature/cache improvements 2 (#1067)
* New Mode to control queryCache behavior

* Collections form the cache have to be read only

* findCount not queried in query cache

* return modifiable collections from the cache

* FIX: Compile errors

* No effective code change: reformat code / javadoc
2017-09-12 23:40:18 +12:00
Roland PramlandRob Bygrave b8e1dffca2 replaced findUnique by "default" method in interfaces. (#1124) 2017-09-12 23:20:04 +12:00
rob bygrave 7e8ed72b7b #1125 - ENH: Add a SpiContainerBootup API ... to enable automatic startup of docker containers (for testing usually) 2017-09-12 22:44:37 +12:00
rob bygrave ec8dd30a04 [maven-release-plugin] prepare for next development iteration 2017-09-11 23:12:43 +12:00
rob bygrave f81d5c9f3a [maven-release-plugin] prepare release ebean-10.4.5 2017-09-11 23:12:29 +12:00
rob bygrave 74b24b1db1 #1120 - Bump classpath scanner dependency to 2.2.4 (fix for NPE on empty directory) 2017-09-11 23:09:28 +12:00
rob bygrave 179ef8c8a0 No effective change - javadoc update on Ebean 2017-09-10 21:23:37 +12:00
rob bygrave f77f3c3d6e #1119 - Deprecate TxRunnable and TxCallable - migrate to plan JDK Runnable and Callable. Add explicit executeCall() method 2017-09-10 20:47:26 +12:00
Roland PramlandRob Bygrave 9a7646195c Featur/dbmigration 3 (#1118)
* ADD: more test classes for Softdelete and History

* update reference models after adding test classes

* DbMigration:Improved History generation for sqlserver

* FIX: When DB supports Sql2011 platform, we must not alter the history-table (as this is handled by DB itself)

* DbMigration: When dropping a table, drop the sequence also.

* DbMigration: dropColumn is handled by platformDdl now, as SqlServerDdl needs to delete dependent objects (default constraints...) first

* FIX: DbMigration is not executed on history table

* DbMigration: can drop comment now.

* No effective code change: completed ObjectFactory / fixed imports

* FIX: drop table comment

* Committed the reference models
2017-09-10 15:26:01 +12:00
rob bygrave 84d3578e33 No effective change - add a test for saveAll() with existing batch mode on
Assert that it does not flush the batch.  We want the batch to continue and flush normally.
2017-09-09 14:14:57 +12:00
rob bygrave ef05b81014 #1117 - Refactor - moved DbDefault and DbMigration annotations to ebean-annotation 2017-09-09 13:27:09 +12:00
rob bygrave 726c682984 #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:18:39 +12:00
rob bygrave 0c22a99fdf #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:12:40 +12:00
rob bygrave c062c8e001 #1054 Java 9 - adding "Automatic-Module-Name" to MANIFEST.MF initial step
Update dependency to ebean-migration 10.2.1, avaje-classpath-scanner and avaje-datasource to avoid package name clash
2017-09-09 01:53:47 +12:00
rob bygrave 06f3c000f5 #1115 - Trim SQL for SqlUpdate, SqlQuery and CallableSql - also replace \n with space 2017-09-08 23:45:28 +12:00
rob bygrave 8c0232fdb8 #1114 - Deprecate findUnique() - please migrate to findOne() - update tests 2017-09-08 22:48:46 +12:00
rob bygrave 2470486f20 #1114 - Deprecate findUnique() - please migrate to findOne() 2017-09-08 22:37:50 +12:00
rob bygrave 8fbf0374dd No effective change - tidy around Db migration changes 2017-09-08 22:02:56 +12:00
Roland PramlandRob Bygrave dd8fff0277 Feature/ddl migration (#1111)
* REFACTOR: use functions from ddl-help

* NEW: Default values are written to DDL

* fix testcase for sqlserver as default constraints will get names in future

* Reading DbMigration

* ADD: DbmIgrationGenerateTest

* Tables must be created first

* Sanity checks and some getters

* DdlMigration gets applied now

* ADD: Testcase and reference migration scripts

* no effective code changes: add comments
2017-09-08 21:47:33 +12:00
Rob Bygrave 73b3d1d977 [maven-release-plugin] prepare for next development iteration 2017-09-07 00:41:49 +12:00
Rob Bygrave 2a76ae2192 [maven-release-plugin] prepare release ebean-10.4.4 2017-09-07 00:41:36 +12:00
Rob Bygrave 39931a3a2c No effective change - add test example of markUnset on version property (to avoid occ check) 2017-09-07 00:40:19 +12:00
Rob Bygrave 58eb49617b #1113 - SqlUpdate with leading spaces leads to Invalid event detection (which table was update for L2 cache invalidation etc) 2017-09-07 00:36:52 +12:00
Rob Bygrave 9b621d2ec7 #1112 - Enhance JDBC batch behavior when cascade persist is turned off (manual master/detail batch persisting) 2017-09-06 23:57:54 +12:00
Roland PramlandRob Bygrave f2e8b652fa FIX: DDL process comment change correctly & drop references when index is dropped (#1109) 2017-09-01 20:45:06 +12:00
Roland PramlandRob Bygrave f342d9d561 API-CHANGE: DbMigration returns which version was generated (#1108) 2017-09-01 20:42:02 +12:00
Roland PramlandRob Bygrave 8009bdf099 REFACTOR: Created a "splitNames" in StringHelper and use it in various places. Also wrote some tests for StringHelper. (#1107) 2017-09-01 20:40:39 +12:00
Roland PramlandRob Bygrave 7336ce81bd FIX: Do not add AssocOne-generatedProperties if they should not. (#1106) 2017-09-01 20:38:13 +12:00
Roland PramlandRob Bygrave f805bec8f2 FIX: Using correct datatype for null value when json is serialized to varchar column (#1105) 2017-09-01 20:36:41 +12:00
Roland PramlandRob Bygrave 46cdc84e6b Refactor/findannotation (#1104)
* refactor: renamed findAnnotation method that searches recursive to findAnnotationRecursive

* CHG: searching namedQueries & indices also recursive in superclasses

* FIX: Using the AnnotationBase methods on several places and documented them
2017-09-01 20:25:10 +12:00
Roland PramlandRob Bygrave e908d16ba8 Pr/add tests (#1103)
* ADD: Testcase for Sqlserver-6.2.0 driver

* CHG: Removed Row-limit - so we need no special handling when sqlserver-platform generates a "select top 100" statement

* ADD: additional asserts to TestQueryCache

* CHG: Ignore test for sqlserver
2017-09-01 20:17:56 +12:00
rob bygrave 96e3f4138f #1064 - Query table alias not used in update() or delete() queries 2017-08-31 21:29:08 +12:00
rob bygrave 977dd52890 No effective change - code reformat only 2017-08-31 21:10:13 +12:00
Roland PramlandRob Bygrave a9b7bb2175 FIX: Passing MDC through background-executor (#1102) 2017-08-31 21:09:04 +12:00
rob bygrave f83c7fdac8 #1100 - Refactor OrmQueryPlanKey to use string expression for where 2017-08-31 02:11:49 +12:00
rob bygrave a695fb59b2 [maven-release-plugin] prepare for next development iteration 2017-08-31 01:50:20 +12:00
446 changed files with 7327 additions and 2427 deletions
+9 -9
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>10.4.3</version>
<version>11.1.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-10.4.3</tag>
<tag>ebean-11.1.1</tag>
</scm>
<profiles>
@@ -87,7 +87,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>2.3</version>
<version>3.1</version>
</dependency>
<dependency>
@@ -99,7 +99,7 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>2.1.1</version>
<version>2.1.2</version>
</dependency>
<dependency>
@@ -111,13 +111,13 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.2</version>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-dbmigration</artifactId>
<version>10.1.10</version>
<artifactId>ebean-migration</artifactId>
<version>10.3.1</version>
</dependency>
<dependency>
@@ -210,7 +210,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>10.4.1</version>
<version>11.1.1</version>
<scope>test</scope>
</dependency>
@@ -283,7 +283,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>10.4.1</version>
<version>11.1.1</version>
<executions>
<execution>
<id>test</id>
+57
View File
@@ -0,0 +1,57 @@
package io.ebean;
/**
* Enum to control the different cache modes for queryCache (and maybe later) beanCache.
*
* If cache is enabled, you must be careful, what you do with the returned collection.
* By default the returned collections are read only and you will get an exception if you try
* to change them.
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
* one in the cache. That means, changing does not affect the cache
*
* @author Roland Praml, FOCONIS AG
*/
public enum CacheMode {
/**
* Do not use cache.
*/
OFF(false, false),
/**
* Use the cace (query & store the resut).
*/
ON(true, true),
/**
* Do not read from cache, but write retrived value to cache.
* Use this, if you want to get the fresh value from database and a CacheMode.ON query will follow.
*/
RECACHE(false, true),
/**
* Query the cache for value. If it is there, use it, otherwise hit database but do NOT put the value
* into the cache. (this mode is for completeness. There's probably no use case for this)
*/
QUERY_ONLY(true,false);
private boolean get;
private boolean put;
private CacheMode(boolean get, boolean put) {
this.get = get;
this.put = put;
}
/**
* Retruns <code>true</code> if value is read from cache.
*/
public boolean isGet() {
return get;
}
/**
* Returns <code>true</code> if value (from database) is written to cache.
*/
public boolean isPut() {
return put;
}
}
+2 -2
View File
@@ -56,7 +56,7 @@ public interface DocumentStore {
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findUnique() on the query.
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
@@ -64,7 +64,7 @@ public interface DocumentStore {
* server.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findUnique();
* .findOne();
*
* }</pre>
*/
+42 -22
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
@@ -14,6 +15,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -289,7 +291,7 @@ public final class Ebean {
}
/**
* Backdoor for registering a mock implementation of EbeanServer as the default server.
* Backdoor for registering a mock implementation of EbeanServer as the default server.
*/
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
@@ -371,9 +373,10 @@ public final class Ebean {
/**
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
*
* <h3>REQUIRES_NEW example:</h3>
* <pre>{@code
* // Start a new transaction. If there is a current transaction
@@ -822,7 +825,7 @@ public final class Ebean {
* }</pre>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <pre>{@code
*
@@ -843,7 +846,8 @@ public final class Ebean {
*
* // traverse the object graph...
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* Customer customer = order.getCustomer();
* Address shippingAddress = customer.getShippingAddress();
* List<OrderDetail> details = order.getDetails();
@@ -959,7 +963,7 @@ public final class Ebean {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -991,7 +995,7 @@ public final class Ebean {
* Query<Order> query = Ebean.createQuery(Order.class, eql);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -1000,7 +1004,7 @@ public final class Ebean {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -1181,20 +1185,20 @@ public final class Ebean {
*
* }</pre>
*/
public static void execute(TxScope scope, TxRunnable r) {
public static void execute(TxScope scope, Runnable r) {
serverMgr.getDefaultServer().execute(scope, r);
}
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
* </p>
* <pre>{@code
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* Ebean.execute(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1203,17 +1207,17 @@ public final class Ebean {
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
*
* });
*
* }</pre>
*/
public static void execute(TxRunnable r) {
public static void execute(Runnable r) {
serverMgr.getDefaultServer().execute(r);
}
/**
* Execute a TxCallable in a Transaction with an explicit scope.
* Execute a Callable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1223,7 +1227,7 @@ public final class Ebean {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxCallable<String>() {
* Ebean.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1233,12 +1237,20 @@ public final class Ebean {
*
* }</pre>
*/
public static <T> T execute(TxScope scope, TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(scope, c);
public static <T> T executeCall(TxScope scope, Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(scope, c);
}
/**
* Execute a TxCallable in a Transaction with the default scope.
* Deprecated - please migrate to executeCall().
*/
@Deprecated
public static <T> T execute(TxScope scope, TxCallable<T> c) {
return serverMgr.getDefaultServer().executeCall(scope, c);
}
/**
* Execute a Callable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1249,8 +1261,8 @@ public final class Ebean {
* </p>
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* Ebean.executeCall(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1261,11 +1273,19 @@ public final class Ebean {
* Ebean.save(u2);
*
* return u1.getEmail();
* }
*
* });
*
* }</pre>
*/
public static <T> T executeCall(Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(c);
}
/**
* Deprecated - please migrate to executeCall().
*/
@Deprecated
public static <T> T execute(TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(c);
}
+50 -30
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
@@ -16,6 +17,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -238,7 +240,7 @@ public interface EbeanServer {
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -247,7 +249,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -265,7 +267,7 @@ public interface EbeanServer {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -285,7 +287,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* // find some new orders ... with firstRow/maxRows
* List<Order> orders =
@@ -560,8 +562,8 @@ public interface EbeanServer {
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
* <p>
* <h3>REQUIRES_NEW example:</h3>
@@ -694,7 +696,7 @@ public interface EbeanServer {
* <p>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <p>
* <pre>{@code
@@ -714,7 +716,7 @@ public interface EbeanServer {
* query.fetch("details.product", "name");
*
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // traverse the object graph...
*
@@ -1088,14 +1090,17 @@ public interface EbeanServer {
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Synonym for findOne().
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
<T> T findUnique(Query<T> query, Transaction transaction);
default <T> T findUnique(Query<T> query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
@@ -1166,7 +1171,7 @@ public interface EbeanServer {
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
@@ -1174,20 +1179,23 @@ public interface EbeanServer {
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findUnique()
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
/**
* Synonym for findOne().
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
SqlRow findUnique(SqlQuery query, Transaction transaction);
default SqlRow findUnique(SqlQuery query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Either Insert or Update the bean depending on its state.
@@ -1585,7 +1593,7 @@ public interface EbeanServer {
int execute(CallableSql callableSql, Transaction transaction);
/**
* Execute a TxRunnable in a Transaction with an explicit scope.
* Execute a Runnable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1596,7 +1604,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxRunnable() {
* ebeanServer.execute(scope, new Runnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1605,10 +1613,10 @@ public interface EbeanServer {
*
* }</pre>
*/
void execute(TxScope scope, TxRunnable runnable);
void execute(TxScope scope, Runnable runnable);
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1616,8 +1624,8 @@ public interface EbeanServer {
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxRunnable() {
* public void run() {
* ebeanServer.execute(() -> {
*
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
*
@@ -1626,12 +1634,12 @@ public interface EbeanServer {
*
* ebeanServer.save(u1);
* ebeanServer.save(u2);
* }
*
* });
*
* }</pre>
*/
void execute(TxRunnable runnable);
void execute(Runnable runnable);
/**
* Execute a TxCallable in a Transaction with an explicit scope.
@@ -1645,7 +1653,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxCallable<String>() {
* ebeanServer.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* ...
@@ -1655,7 +1663,15 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxScope scope, TxCallable<T> callable);
<T> T executeCall(TxScope scope, Callable<T> callable);
/**
* Deprecated - please migrate to executeCall().
*/
@Deprecated
default <T> T execute(TxScope scope, TxCallable<T> callable) {
return executeCall(scope, callable);
}
/**
* Execute a TxCallable in a Transaction with the default scope.
@@ -1664,13 +1680,9 @@ public interface EbeanServer {
* exception (checked or runtime).
* </p>
* <p>
* This is basically the same as TxRunnable except that it returns an Object
* (and you specify the return type via generics).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxCallable<String>() {
* ebeanServer.executeCall(new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
@@ -1687,7 +1699,15 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxCallable<T> callable);
<T> T executeCall(Callable<T> callable);
/**
* Deprecated - please migrate to executeCall().
*/
@Deprecated
default <T> T execute(TxCallable<T> callable) {
return executeCall(null, callable);
}
/**
* Return the manager of the server cache ("L2" cache).
+17 -5
View File
@@ -264,7 +264,7 @@ public interface ExpressionList<T> {
* </p>
*
* @throws NonUniqueResultException if more than one result was found
* @see Query#findUnique()
* @see Query#findOne()
*/
@Nullable
T findOne();
@@ -275,14 +275,17 @@ public interface ExpressionList<T> {
Optional<T> findOneOrEmpty();
/**
* Synonym for findOne().
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
T findUnique();
default T findUnique() {
return findOne();
}
/**
* Execute find row count query in a background thread.
@@ -442,12 +445,21 @@ public interface ExpressionList<T> {
Query<T> setUseCache(boolean useCache);
/**
* Set to true to use the query for executing this query.
* Set the {@link CacheMode} to use the query for executing this query.
*
* @see Query#setUseQueryCache(boolean)
*/
Query<T> setUseQueryCache(boolean useCache);
Query<T> setUseQueryCache(CacheMode useCache);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*
* @see Query#setUseQueryCache(CacheMode)
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
* <p>
+1 -1
View File
@@ -27,7 +27,7 @@ import java.util.List;
* // Add your customer finder methods ...
*
* public Customer byName(String name) {
* return query().eq("name", name).findUnique();
* return query().eq("name", name).findOne();
* }
*
* public List<Customer> findNew() {
+19 -9
View File
@@ -831,7 +831,7 @@ public interface Query<T> {
* Product product =
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* .findOne();
* ...
* }</pre>
* <p>
@@ -845,7 +845,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
@@ -863,14 +863,17 @@ public interface Query<T> {
Optional<T> findOneOrEmpty();
/**
* Synonym for findOne().
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
T findUnique();
default T findUnique() {
return findOne();
}
/**
* Return versions of a @History entity bean.
@@ -1028,7 +1031,7 @@ public interface Query<T> {
Query<T> setParameter(int position, Object value);
/**
* Set the Id value to query. This is used with findUnique().
* Set the Id value to query. This is used with findOne().
* <p>
* You can use this to have further control over the query. For example adding
* fetch joins.
@@ -1039,7 +1042,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
@@ -1322,10 +1325,17 @@ public interface Query<T> {
Query<T> setUseCache(boolean useCache);
/**
* Set this to true to use the query cache.
* Set the {@link CacheMode} to use the query for executing this query.
*/
Query<T> setUseQueryCache(boolean useQueryCache);
Query<T> setUseQueryCache(CacheMode useQueryCache);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
* <p>
+5 -2
View File
@@ -79,9 +79,12 @@ public interface SqlQuery extends Serializable {
Optional<SqlRow> findOneOrEmpty();
/**
* Synonym for findOne().
* Deprecated - please migrate to findOne().
*/
SqlRow findUnique();
@Deprecated
default SqlRow findUnique() {
return findOne();
}
/**
* The same as bind for named parameters.
+1
View File
@@ -1,6 +1,7 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DocStoreConfig;
import io.ebean.config.ServerConfig;
+8 -4
View File
@@ -1,9 +1,13 @@
package io.ebean;
import java.util.concurrent.Callable;
/**
* Deprecated - please migrate to just using Callable instead with executeCall().
*
* Execute a TxCallable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxCallable)} method.
* Use this with the {@link Ebean#executeCall(Callable)} method.
* </p>
* <p>
* Note that this is basically the same as TxRunnable except that it returns an
@@ -31,10 +35,9 @@ package io.ebean;
* });
*
* }</pre>
*
* @see TxRunnable
*/
public interface TxCallable<T> {
@Deprecated
public interface TxCallable<T> extends Callable<T> {
/**
* Execute the method within a transaction scope returning the result.
@@ -43,5 +46,6 @@ public interface TxCallable<T> {
* instead.
* </p>
*/
@Override
T call();
}
+7 -5
View File
@@ -1,12 +1,12 @@
package io.ebean;
/**
* Deprecated - please migrate to using just Runnable.
*
*
* Execute a TxRunnable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxRunnable)} method.
* </p>
* <p>
* See also {@link TxCallable}.
* Use this with the {@link Ebean#execute(Runnable)} method.
* </p>
* <p>
* <pre>{@code
@@ -31,10 +31,12 @@ package io.ebean;
*
* @see TxCallable
*/
public interface TxRunnable {
@Deprecated
public interface TxRunnable extends Runnable {
/**
* Run the method in a transaction sope.
*/
@Override
void run();
}
+30 -7
View File
@@ -1,24 +1,27 @@
package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Holds the definition of how a transactional method should run.
* <p>
* This information matches the features of the Transactional annotation. You
* can use it directly with TxRunnable or TxCallable via
* {@link Ebean#execute(TxScope, TxCallable)} or
* {@link Ebean#execute(TxScope, TxRunnable)}.
* can use it directly with Runnable or Callable via
* {@link Ebean#execute(TxScope, Runnable)} or
* {@link Ebean#executeCall(TxScope, Callable)}.
* </p>
* <p>
* This object is used internally with the enhancement of a method with
* Transactional annotation.
* </p>
*
* @see TxCallable
* @see TxRunnable
* @see Ebean#execute(TxScope, TxCallable)
* @see Ebean#execute(TxScope, TxRunnable)
* @see Ebean#execute(TxScope, Runnable)
* @see Ebean#executeCall(TxScope, Callable)
*/
public final class TxScope {
@@ -38,6 +41,11 @@ public final class TxScope {
boolean readOnly;
/**
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
*/
boolean flushOnQuery = true;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
@@ -235,6 +243,21 @@ public final class TxScope {
return this;
}
/**
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
*/
public boolean isFlushOnQuery() {
return flushOnQuery;
}
/**
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
*/
public TxScope setFlushOnQuery(boolean flushOnQuery) {
this.flushOnQuery = flushOnQuery;
return this;
}
/**
* Return the Isolation level this transaction should run with.
*/
@@ -226,4 +226,9 @@ public interface BeanCollection<E> extends Serializable {
* additions and removals have been processed.
*/
void modifyReset();
/**
* Return a shallow copy of this collection that is modifiable.
*/
BeanCollection<E> getShallowCopy();
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* Visitor for collecting new/old values for a bean update.
*/
public interface BeanDiffVisitor {
/**
* Collect a new/old value pair.
*/
void visit(int position, Object newVal, Object oldVal);
/**
* Start processing an associated bean.
*/
void visitPush(int position);
/**
* Stop processing an associated bean.
*/
void visitPop();
}
@@ -507,6 +507,15 @@ public final class EntityBeanIntercept implements Serializable {
loadedProps[propertyIndex] = true;
}
/**
* Set all properties to be loaded (post insert).
*/
public void setLoadedPropertyAll() {
for (int i = 0; i < loadedProps.length; i++) {
loadedProps[i] = true;
}
}
/**
* Return true if the property is loaded.
*/
@@ -694,6 +703,28 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Recursively add dirty properties.
*/
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
visitor.visit(i, newVal, oldVal);
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
visitor.visitPush(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
visitor.visitPop();
}
}
}
/**
* Return a dirty property hash taking into account embedded beans.
*/
@@ -1124,4 +1155,11 @@ public final class EntityBeanIntercept implements Serializable {
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Explicitly set an old value.
*/
public void setOldValue(int propertyIndex,Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
}
@@ -213,4 +213,16 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
*/
protected void setFromOriginal(AbstractBeanCollection<E> other) {
this.disableLazyLoad = other.disableLazyLoad;
this.ebeanServerName = other.ebeanServerName;
this.loader = other.loader;
this.ownerBean = other.ownerBean;
this.propertyName = other.propertyName;
}
}
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
copy.setFromOriginal(this);
return copy;
}
}
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
return map.values();
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
}
+7 -1
View File
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -0,0 +1,180 @@
package io.ebean.common;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
* to modify lists from cache.
*
* @author Roland Praml, FOCONIS AG
*/
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
private static final long serialVersionUID = 1L;
/**
* The underlying List implementation.
*/
private List<E> list;
public CopyOnFirstWriteList(List<E> list) {
super();
this.list = list;
}
private volatile boolean copied = false;
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
checkCopyOnWrite();
return list.add(e);
}
@Override
public boolean remove(Object o) {
checkCopyOnWrite();
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
checkCopyOnWrite();
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
checkCopyOnWrite();
return list.retainAll(c);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
checkCopyOnWrite();
list.replaceAll(operator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
checkCopyOnWrite();
return list.removeIf(filter);
}
@Override
public void sort(Comparator<? super E> c) {
checkCopyOnWrite();
list.sort(c);
}
@Override
public void clear() {
if (!copied) {
list = new ArrayList<>();
copied = true;
}
}
@Override
public boolean equals(Object o) {
return list.equals(o);
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
checkCopyOnWrite();
return list.set(index, element);
}
@Override
public void add(int index, E element) {
checkCopyOnWrite();
list.add(index, element);
}
@Override
public E remove(int index) {
checkCopyOnWrite();
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
private void checkCopyOnWrite() {
if (!copied) {
synchronized (this) {
if (!copied) {
list = new ArrayList<>(list);
copied = true;
}
}
}
}
}
@@ -1,13 +1,13 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import java.lang.annotation.Annotation;
/**
* Provides some base implementation for NamingConventions.
@@ -219,15 +219,10 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* Return true if this class is part of entity inheritance.
*/
protected boolean hasInheritance(Class<?> supCls) {
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
return AnnotationBase.findAnnotationRecursive(supCls, Inheritance.class) != null
|| AnnotationBase.findAnnotation(supCls, DiscriminatorValue.class) != null;
}
/**
* Return true if the class has the given annotation.
*/
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
return supCls.getAnnotation(annotation) != null;
}
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
@@ -258,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
*/
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
final Table t = findTableAnnotation(beanClass);
final Table t = AnnotationBase.findAnnotationRecursive(beanClass, Table.class);
// Take the annotation if defined
if (t != null && !isEmpty(t.name())) {
@@ -271,22 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
/**
* Search recursively for an @Table in the class hierarchy.
*/
protected Table findTableAnnotation(Class<?> cls) {
while (true) {
if (cls.equals(Object.class)) {
return null;
}
Table table = cls.getAnnotation(Table.class);
if (table != null) {
return table;
}
cls = cls.getSuperclass();
}
}
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
@@ -1,7 +1,7 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
/**
* Custom mappings for DB types that override the default.
@@ -1,8 +1,8 @@
package io.ebean.config;
import io.ebean.Platform;
import io.ebean.dbmigration.MigrationConfig;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.annotation.Platform;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -2,7 +2,7 @@ package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import java.util.ArrayList;
import java.util.List;
+49 -12
View File
@@ -2,9 +2,9 @@ package io.ebean.config;
import com.fasterxml.jackson.core.JsonFactory;
import io.ebean.EbeanServerFactory;
import io.ebean.PersistBatch;
import io.ebean.annotation.PersistBatch;
import io.ebean.PersistenceContextScope;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.Encrypted;
@@ -13,7 +13,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbType;
import io.ebean.dbmigration.DbOffline;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.migration.MigrationRunner;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
@@ -28,7 +28,8 @@ import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import java.util.regex.Pattern;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
@@ -77,8 +78,6 @@ import java.util.ServiceLoader;
*/
public class ServerConfig {
private static final Pattern NAMES_SPLIT = Pattern.compile("[ ,;]");
/**
* The EbeanServer name.
*/
@@ -440,6 +439,16 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
private long slowQueryMillis;
/**
* The listener for processing slow query events.
*/
private SlowQueryListener slowQueryListener;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -447,6 +456,34 @@ public class ServerConfig {
}
/**
* Return the slow query time in millis.
*/
public long getSlowQueryMillis() {
return slowQueryMillis;
}
/**
* Set the slow query time in millis.
*/
public void setSlowQueryMillis(long slowQueryMillis) {
this.slowQueryMillis = slowQueryMillis;
}
/**
* Return the slow query event listener.
*/
public SlowQueryListener getSlowQueryListener() {
return slowQueryListener;
}
/**
* Set the slow query event listener.
*/
public void setSlowQueryListener(SlowQueryListener slowQueryListener) {
this.slowQueryListener = slowQueryListener;
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
@@ -2535,6 +2572,7 @@ public class ServerConfig {
dbTypeConfig.setGeometrySRID(srid);
}
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
@@ -2647,10 +2685,9 @@ public class ServerConfig {
List<Class<?>> classes = new ArrayList<>();
String[] split = NAMES_SPLIT.split(classNames);
for (String aSplit : split) {
String cn = aSplit.trim();
if (!cn.isEmpty() && !"class".equalsIgnoreCase(cn)) {
String[] split = StringHelper.splitNames(classNames);
for (String cn : split) {
if (!"class".equalsIgnoreCase(cn)) {
try {
classes.add(Class.forName(cn));
} catch (ClassNotFoundException e) {
@@ -2668,9 +2705,9 @@ public class ServerConfig {
if (searchPackages != null) {
String[] entries = NAMES_SPLIT.split(searchPackages);
String[] entries = StringHelper.splitNames(searchPackages);
for (String entry : entries) {
hitList.add(entry.trim());
hitList.add(entry);
}
}
return hitList;
@@ -0,0 +1,59 @@
package io.ebean.config;
import io.ebean.bean.ObjectGraphNode;
/**
* Slow query event.
*/
public class SlowQueryEvent {
private final String sql;
private final long timeMillis;
private final int rowCount;
private final ObjectGraphNode originNode;
/**
* Construct with the SQL and execution time in millis.
*/
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
this.sql = sql;
this.timeMillis = timeMillis;
this.rowCount = rowCount;
this.originNode = originNode;
}
/**
* Return the SQL for the slow query.
*/
public String getSql() {
return sql;
}
/**
* Return the execution time in millis.
*/
public long getTimeMillis() {
return timeMillis;
}
/**
* Return the total row count associated with the query.
*/
public int getRowCount() {
return rowCount;
}
/**
* Return the origin point for the root query.
* <p>
* Typically the <code>originNode.getOriginQueryPoint().getFirstStackElement()</code> provides the stack line that
* shows the code that invoked the query.
* </p>
*/
public ObjectGraphNode getOriginNode() {
return originNode;
}
}
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* Listener for slow query events.
*/
@FunctionalInterface
public interface SlowQueryListener {
/**
* Process a slow query event.
*/
void process(SlowQueryEvent event);
}
@@ -4,8 +4,8 @@ import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.config.CustomDbTypeMapping;
import io.ebean.config.DbTypeConfig;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
@@ -287,6 +287,9 @@ public class DatabasePlatform {
* Create and return a DDL handler for generating DDL scripts.
*/
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
if (platformDdl == null) {
throw new IllegalStateException("Platform " + getName() + " has no DDL Handler");
}
return platformDdl.createDdlHandler(serverConfig);
}
@@ -1,8 +1,13 @@
package io.ebean.config.dbplatform;
import java.sql.Types;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import io.ebean.annotation.DbDefault;
/**
* DB Column default values mapping to database platform specific literals.
*/
@@ -22,6 +27,13 @@ public class DbDefaultValue {
* The key for the NOW / current timestamp.
*/
public static final String NOW = "now";
/**
* The 'null' literal.
*/
public static final String NULL = "null";
protected Map<String, String> map = new LinkedHashMap<>();
@@ -63,8 +75,128 @@ public class DbDefaultValue {
if (dbDefaultLiteral == null) {
return null;
}
if (dbDefaultLiteral.startsWith("$RAW:")) {
return dbDefaultLiteral.substring(5);
}
String val = map.get(dbDefaultLiteral);
return val != null ? val : dbDefaultLiteral;
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
* Note: There are some special cases:
* </p>
* <ul>
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
* returns: <code>default 'User''s default'</code><br/>
* (the same on an integer property will throw a NumberFormatException)</li>
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
* which gives you the <code>default 'null'</code> statement.</li>
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
* </ul>
*/
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
if (propertyType == null
|| defaultValue == null
|| NULL.equals(defaultValue)
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|| (defaultValue.startsWith("$RAW:"))) {
return defaultValue;
}
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
return toBooleanLiteral(defaultValue);
}
if (Number.class.isAssignableFrom(propertyType)
|| Byte.TYPE.equals(propertyType)
|| Short.TYPE.equals(propertyType)
|| Integer.TYPE.equals(propertyType)
|| Long.TYPE.equals(propertyType)
|| Float.TYPE.equals(propertyType)
|| Double.TYPE.equals(propertyType)
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
Double.valueOf(defaultValue); // verify if it is a number
return defaultValue;
}
// check if it is a date/time - in all other cases return quoted defaultValue
switch (sqlType) {
// date
case Types.DATE:
return toDateLiteral(defaultValue);
// time
case Types.TIME:
case Types.TIME_WITH_TIMEZONE:
return toTimeLiteral(defaultValue);
// timestamp
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
return toDateTimeLiteral(defaultValue);
default:
return toTextLiteral(defaultValue); // do not check other datatypes
}
}
/**
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
*/
private static String toBooleanLiteral(String value) {
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
return value;
}
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
}
/**
* This adds single qoutes around the <code>value</code> and doubles single quotes.
* "User's home" will return "'User''s home'"
*/
private static String toTextLiteral(String value) {
StringBuilder sb = new StringBuilder(value.length()+10);
sb.append('\'');
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch == '\'') {
sb.append("''");
} else {
sb.append(ch);
}
}
sb.append('\'');
return sb.toString();
}
private static String toDateLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDate(value); // verify
return toTextLiteral(value);
}
private static String toTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseTime(value); // verify
return toTextLiteral(value);
}
private static String toDateTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDateTime(value); // verify
return toTextLiteral(value);
}
}
@@ -1,8 +1,8 @@
package io.ebean.config.dbplatform.db2;
import io.ebean.BackgroundExecutor;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -44,7 +44,7 @@ public class DB2Platform extends DatabasePlatform {
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 15));
persistBatchOnCascade = PersistBatch.NONE;
}
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.h2;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.hsqldb;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.BasicSqlAnsiLimiter;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.Query;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -1,6 +1,6 @@
package io.ebean.config.dbplatform.sqlanywhere;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -1,6 +1,6 @@
package io.ebean.config.dbplatform.sqlite;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -4,15 +4,17 @@ import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.DbMigrationConfig;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
import io.ebean.dbmigration.migration.Migration;
@@ -196,8 +198,9 @@ public class DbMigration {
* migration.generateMigration();
*
* }</pre>
* @return the generated migration or null
*/
public void generateMigration() throws IOException {
public String generateMigration() throws IOException {
// use this flag to stop other plugins like full DDL generation
if (!online) {
@@ -218,9 +221,9 @@ public class DbMigration {
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
return generatePendingDrop(request, pendingVersion);
} else {
generateDiff(request);
return generateDiff(request);
}
} finally {
@@ -276,7 +279,7 @@ public class DbMigration {
/**
* Generate the diff migration.
*/
private void generateDiff(Request request) throws IOException {
private String generateDiff(Request request) throws IOException {
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
@@ -286,25 +289,27 @@ public class DbMigration {
Migration migration = request.createDiffMigration();
if (migration == null) {
logger.info("no changes detected - no migration written");
return null;
} else {
// there were actually changes to write
generateMigration(request, migration, null);
return generateMigration(request, migration, null);
}
}
/**
* Generate the migration based on the pendingDrops from a prior version.
*/
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
private String generatePendingDrop(Request request, String pendingVersion) throws IOException {
Migration migration = request.migrationForPendingDrop(pendingVersion);
generateMigration(request, migration, pendingVersion);
String version = generateMigration(request, migration, pendingVersion);
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
}
return version;
}
private Request createRequest() {
@@ -358,14 +363,14 @@ public class DbMigration {
}
}
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
logger.warn("migration already exists, not generating DDL");
return null;
} else {
if (!platforms.isEmpty()) {
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
@@ -377,6 +382,7 @@ public class DbMigration {
PlatformDdlWriter writer = createDdlWriter(databasePlatform);
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
return fullVersion;
}
}
@@ -515,18 +521,24 @@ public class DbMigration {
switch (platform) {
case H2:
return new H2Platform();
case HSQLDB:
return new HsqldbPlatform();
case POSTGRES:
return new PostgresPlatform();
case MYSQL:
return new MySqlPlatform();
case ORACLE:
return new OraclePlatform();
case SQLANYWHERE:
return new SqlAnywherePlatform();
case SQLSERVER:
return new SqlServerPlatform();
case DB2:
return new DB2Platform();
case SQLITE:
return new SQLitePlatform();
case GENERIC:
return new DatabasePlatform();
default:
throw new IllegalArgumentException("Platform missing? " + platform);
@@ -1,6 +1,6 @@
package io.ebean.dbmigration;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -5,7 +5,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.dbmigration.model.CurrentModel;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import io.ebean.dbmigration.ddl.DdlRunner;
import io.ebean.migration.ddl.DdlRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,6 +36,11 @@ public class BaseDdlHandler implements DdlHandler {
for (Object change : changeSetChildren) {
if (change instanceof CreateTable) {
generate(writer, (CreateTable) change);
}
}
for (Object change : changeSetChildren) {
if (change instanceof CreateTable) {
// ignore
} else if (change instanceof DropTable) {
generate(writer, (DropTable) change);
} else if (change instanceof AddTableComment) {
@@ -54,6 +59,8 @@ public class BaseDdlHandler implements DdlHandler {
generate(writer, (AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
generate(writer, (DropHistoryTable) change);
} else {
throw new IllegalArgumentException("Unsupported change: " + change);
}
}
}
@@ -3,6 +3,7 @@ package io.ebean.dbmigration.ddlgeneration.platform;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.NamingConvention;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.dbplatform.IdType;
import io.ebean.dbmigration.ddlgeneration.DdlBuffer;
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
@@ -15,6 +16,7 @@ import io.ebean.dbmigration.migration.AlterColumn;
import io.ebean.dbmigration.migration.Column;
import io.ebean.dbmigration.migration.CreateIndex;
import io.ebean.dbmigration.migration.CreateTable;
import io.ebean.dbmigration.migration.DdlScript;
import io.ebean.dbmigration.migration.DropColumn;
import io.ebean.dbmigration.migration.DropHistoryTable;
import io.ebean.dbmigration.migration.DropIndex;
@@ -27,6 +29,8 @@ import io.ebean.util.StringHelper;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -70,6 +74,121 @@ public class BaseTableDdl implements TableDdl {
*/
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>();
private boolean strict;
private final boolean sql2011History;
/**
* Helper class that is used to execute the migration ddl before and after the migration action.
*/
private class DdlMigrationHelp {
private List<String> before;
private List<String> after;
private String tableName;
private String columnName;
private String defaultValue;
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) throws IOException {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
}
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
}
/**
* Constructor for DdlMigrationHelp when altering a column.
*/
DdlMigrationHelp(AlterColumn alter) throws IOException {
this.tableName = alter.getTableName();
this.columnName = alter.getColumnName();
String tmp = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
this.defaultValue = platformDdl.convertDefaultValue(tmp);
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
}
before = Arrays.asList(platformDdl.getUpdateNullWithDefault());
} else {
before = getScriptsForPlatform(alter.getBefore(), platformDdl.getPlatform().getName());
}
after = getScriptsForPlatform(alter.getAfter(), platformDdl.getPlatform().getName());
}
public void writeBefore(DdlBuffer buffer) throws IOException {
if (!before.isEmpty()) {
buffer.end();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
}
}
public void writeAfter(DdlBuffer buffer) throws IOException {
// here we run postmigration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
buffer.endOfStatement();
}
if (!after.isEmpty()) {
buffer.end();
}
}
private List<String> getScriptsForPlatform(List<DdlScript> scripts, String searchPlatform) {
List<String> ret = Collections.emptyList();
for (DdlScript script : scripts) {
if (script.getPlatforms() == null || script.getPlatforms().isEmpty()) {
ret = script.getDdl();
} else for (String platform : StringHelper.splitNames(script.getPlatforms())) {
if (platform.equals(searchPlatform)) {
return script.getDdl();
}
}
}
return ret;
}
/**
* Replaces Table name (${table}), Column name (${column}) and default value (${default}) in DDL.
*/
private String translate(String ddl, String tableName, String columnName, String defaultValue) {
String ret = StringHelper.replaceString(ddl, "${table}", tableName);
ret = StringHelper.replaceString(ret, "${column}", columnName);
return StringHelper.replaceString(ret, "${default}", defaultValue);
}
private void handleStrictError(String message) {
if (strict) {
throw new IllegalArgumentException(message);
} else {
System.err.println("Error in DDL: " + message);
}
}
public String getDefaultValue() {
return defaultValue;
}
}
/**
* Construct with a naming convention and platform specific DDL.
*/
@@ -79,6 +198,9 @@ public class BaseTableDdl implements TableDdl {
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
this.platformDdl = platformDdl;
this.platformDdl.configure(serverConfig);
this.strict = true; // TODO RPr serverConfig.getMigrationConfig().isStrict();
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
this.sql2011History = hist != null && hist.isStandardsBased();
}
/**
@@ -395,6 +517,14 @@ public class BaseTableDdl implements TableDdl {
buffer.append(platformDdl.dropTable(tableName)).endOfStatement();
}
/**
* Add 'drop sequence' statement to the buffer.
*/
protected void dropSequence(DdlBuffer buffer, String sequenceName) throws IOException {
buffer.append(platformDdl.dropSequence(sequenceName)).endOfStatement();
}
/**
* Write all the check constraints.
*/
@@ -598,7 +728,7 @@ public class BaseTableDdl implements TableDdl {
alterTableAddColumn(writer.apply(), tableName, column, false);
}
if (isTrue(addColumn.isWithHistory())) {
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
// make same changes to the history table
String historyTable = historyTable(tableName);
for (Column column : columns) {
@@ -624,6 +754,15 @@ public class BaseTableDdl implements TableDdl {
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
dropTable(writer.apply(), dropTable.getName());
if (hasValue(dropTable.getSequenceCol())
&& platformDdl.getPlatform().getDbIdentity().isSupportsSequence()) {
String sequenceName = dropTable.getSequenceName();
if (!hasValue(sequenceName)) {
sequenceName = namingConvention.getSequenceName(dropTable.getName(), dropTable.getSequenceCol());
}
dropSequence(writer.apply(), sequenceName);
}
}
/**
@@ -635,7 +774,7 @@ public class BaseTableDdl implements TableDdl {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory())) {
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
// also drop from the history table
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
@@ -649,7 +788,9 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
DdlMigrationHelp ddlHelp = new DdlMigrationHelp(alterColumn);
ddlHelp.writeBefore(writer.apply());
if (isTrue(alterColumn.isHistoryExclude())) {
regenerateHistoryTriggers(alterColumn.getTableName(), HistoryTableUpdate.Change.EXCLUDE, alterColumn.getColumnName());
} else if (isFalse(alterColumn.isHistoryExclude())) {
@@ -702,6 +843,7 @@ public class BaseTableDdl implements TableDdl {
// add constraint last (after potential type change)
addCheckConstraint(writer, alterColumn);
}
ddlHelp.writeAfter(writer.apply());
}
private void alterColumnComment(DdlWrite writer, AlterColumn alterColumn) throws IOException {
@@ -733,7 +875,7 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null) {
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
// mysql and sql server column type change allowing nulls in the history table column
AlterColumn alterHistoryColumn = new AlterColumn();
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
@@ -784,7 +926,7 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory())) {
if (isTrue(alter.isWithHistory()) && !sql2011History) {
// apply same type change to matching column in the history table
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
writer.apply().append(ddl).endOfStatement();
@@ -850,16 +992,19 @@ public class BaseTableDdl implements TableDdl {
protected void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
.endOfStatement();
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
}
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
String ddl = platformDdl.alterTableAddColumn(tableName, column, onHistoryTable);
if (hasValue(ddl)) {
buffer.append(ddl);
buffer.endOfStatement();
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
if (!onHistoryTable) {
help.writeBefore(buffer);
}
platformDdl.alterTableAddColumn(buffer, tableName, column, onHistoryTable, help.getDefaultValue());
if (!onHistoryTable) {
help.writeAfter(buffer);
}
}
@@ -17,6 +17,7 @@ public class DB2Ddl extends PlatformDdl {
this.inlineUniqueWhenNullable = false;
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, boolean notNull) {
if (notNull) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, true);
@@ -0,0 +1,22 @@
package io.ebean.dbmigration.ddlgeneration.platform;
public class DdlHelp {
public static final String DROP_DEFAULT = "DROP DEFAULT";
public static final String DROP_COMMENT = "DROP COMMENT";
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropDefault(String defaultValue) {
return DROP_DEFAULT.equals(defaultValue);
}
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public static boolean isDropComment(String comment) {
return DROP_COMMENT.equals(comment);
}
}
@@ -59,7 +59,7 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
@@ -67,7 +67,9 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
return null;
}
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
@@ -18,6 +18,8 @@ import io.ebean.dbmigration.migration.DropHistoryTable;
import io.ebean.dbmigration.migration.IdentityType;
import io.ebean.dbmigration.model.MTable;
import io.ebean.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
@@ -27,6 +29,8 @@ import java.util.List;
*/
public class PlatformDdl {
private static final Logger logger = LoggerFactory.getLogger(PlatformDdl.class);
protected final DatabasePlatform platform;
protected PlatformHistoryDdl historyDdl = new NoHistorySupportDdl();
@@ -75,7 +79,7 @@ public class PlatformDdl {
protected String dropUniqueConstraint = "drop constraint";
protected String addConstraint = "add constraint";
protected String addColumn = "add column";
protected String columnSetType = "";
@@ -88,6 +92,8 @@ public class PlatformDdl {
protected String columnSetNull = "set null";
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
*/
@@ -205,7 +211,7 @@ public class PlatformDdl {
/**
* Convert the DB column default literal to platform specific.
*/
private String convertDefaultValue(String dbDefault) {
public String convertDefaultValue(String dbDefault) {
return dbDefaultValue.convert(dbDefault);
}
@@ -378,26 +384,44 @@ public class PlatformDdl {
appendColumns(columns, buffer);
return buffer.toString();
}
public String alterTableAddColumn(String tableName, Column column, boolean onHistoryTable) throws IOException {
public void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, String defaultValue) throws IOException {
String convertedType = convert(column.getType(), false);
StringBuilder buffer = new StringBuilder(90);
buffer.append("alter table ").append(tableName)
.append(' ').append(addColumn).append(' ').append(column.getName())
.append(' ').append(convertedType);
.append(" ").append(addColumn).append(" ").append(column.getName())
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
if (!StringHelper.isNull(column.getCheckConstraint())) {
buffer.append(" constraint ").append(column.getCheckConstraintName());
buffer.append(" ").append(column.getCheckConstraint());
if (defaultValue != null) {
if (typeContainsDefault(convertedType)) {
logger.error("Cannot set default value for '" + tableName + "." + column.getName() + "'");
} else {
buffer.append(" default ");
buffer.append(defaultValue);
}
}
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
buffer.append(ddl).endOfStatement();
}
} else {
buffer.endOfStatement();
}
return buffer.toString();
}
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
.endOfStatement();
}
/**
@@ -440,19 +464,11 @@ public class PlatformDdl {
return "alter table " + tableName + " " + addConstraint + " " + checkConstraintName + " " + checkConstraint;
}
/**
* Return true if the default value is the special DROP DEFAULT value.
*/
public boolean isDropDefault(String defaultValue) {
return "DROP DEFAULT".equals(defaultValue);
}
/**
* Alter column setting the default value.
*/
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
}
@@ -505,6 +521,13 @@ public class PlatformDdl {
return naming.lowerColumnName(name);
}
public DatabasePlatform getPlatform() {
return platform;
}
public String getUpdateNullWithDefault() {
return updateNullWithDefault;
}
/**
* Null safe Boolean true test.
@@ -524,15 +547,19 @@ public class PlatformDdl {
* Add table comment as a separate statement (from the create table statement).
*/
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) throws IOException {
if (DdlHelp.isDropComment(tableComment)) {
tableComment = "";
}
apply.append(String.format("comment on table %s is '%s'", tableName, tableComment)).endOfStatement();
}
/**
* Add column comment as a separate statement.
*/
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
if (DdlHelp.isDropComment(comment)) {
comment = "";
}
apply.append(String.format("comment on column %s.%s is '%s'", table, column, comment)).endOfStatement();
}
}
@@ -25,7 +25,12 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String dropTable(String tableName) {
return "IF OBJECT_ID('" + tableName + "', 'U') IS NOT NULL drop table " + tableName;
StringBuilder buffer = new StringBuilder();
buffer.append("IF OBJECT_ID('");
buffer.append(tableName);
buffer.append("', 'U') IS NOT NULL drop table ");
buffer.append(tableName);
return buffer.toString();
}
@Override
@@ -76,7 +81,25 @@ public class SqlServerDdl extends PlatformDdl {
}
return sb.toString();
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
StringBuilder sb = new StringBuilder();
sb.append("IF (OBJECT_ID('").append(constraintName).append("', 'C') IS NOT NULL) ");
sb.append(super.alterTableDropConstraint(tableName, constraintName));
return sb.toString();
}
/**
* Drop a unique constraint from the table (Sometimes this is an index).
*/
@Override
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
StringBuilder sb = new StringBuilder();
sb.append("IF (OBJECT_ID('").append(uniqueConstraintName).append("', 'UQ') IS NOT NULL) ");
sb.append(super.alterTableDropUniqueConstraint(tableName, uniqueConstraintName)).append(";\n");
sb.append(dropIndex(uniqueConstraintName, tableName));
return sb.toString();
}
/**
* Generate and return the create sequence DDL.
*/
@@ -102,17 +125,31 @@ public class SqlServerDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
if (isDropDefault(defaultValue)) {
return "-- alter table " + tableName + " drop constraint <unknown> -- find the appropriate constraint for default value on column " + columnName;
// Unfortunately, the SqlServer creates default values with a random name.
// You can specify a name in DDL, but this does not work in conjunction with
// temporal tables in certain cases. So we have to delete the constraint with
// a rather complex statement.
StringBuilder sb = new StringBuilder();
if (DdlHelp.isDropDefault(defaultValue)) {
sb.append("delimiter $$\n");
sb.append("DECLARE @Tmp nvarchar(200);");
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
.append("') and t2.name = '").append(columnName).append("';\n");
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
} else {
return "alter table " + tableName + " add default " + defaultValue + " for " + columnName;
sb.append("alter table ").append(tableName);
sb.append(" add default ").append(defaultValue).append(" for ").append(columnName);
}
return sb.toString();
}
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
if (DdlHelp.isDropDefault(alter.getDefaultValue())) {
return null;
}
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String type = alter.getType() != null ? alter.getType() : alter.getCurrentType();
@@ -154,4 +191,28 @@ public class SqlServerDdl extends PlatformDdl {
// do nothing for MS SQL Server (cause it requires stored procedures etc)
}
/**
* It is rather complex to delete a column on SqlServer as there must not exist any references
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
* indices over multiple columns will not yet deleted.
* (This may be changed to delete all refering objects by using the sys.* tables later)
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();
buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
buffer.endOfStatement();
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
buffer.endOfStatement();
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
buffer.endOfStatement();
super.alterTableDropColumn(buffer, tableName, columnName);
}
}
@@ -16,11 +16,13 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
private String systemPeriodStart;
private String systemPeriodEnd;
private PlatformDdl platformDdl;
@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "From";
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "To";
this.platformDdl = platformDdl;
}
@Override
@@ -52,9 +54,19 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistory();
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
// drop default constraints
apply.append(platformDdl.alterColumnDefaultValue(baseTable, systemPeriodStart, DdlHelp.DROP_DEFAULT)).endOfStatement();
apply.append(platformDdl.alterColumnDefaultValue(baseTable, systemPeriodEnd, DdlHelp.DROP_DEFAULT)).endOfStatement();
// switch of versioning & period
apply.append("alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
// now drop tables & columns
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodStart).endOfStatement();
apply.append("alter table ").append(baseTable).append(" drop column ").append(systemPeriodEnd).endOfStatement();
apply.append("IF OBJECT_ID('").append(baseTable).append("_history', 'U') IS NOT NULL drop table ").append(baseTable).append("_history").endOfStatement();
apply.end();
}
@Override
@@ -1,8 +1,12 @@
package io.ebean.dbmigration.migration;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
@@ -44,10 +48,17 @@ import javax.xml.bind.annotation.XmlType;
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "")
@XmlType(name = "", propOrder = {
"before", "after"
})
@XmlRootElement(name = "alterColumn")
public class AlterColumn {
@XmlElement(required = false)
protected List<DdlScript> before;
@XmlElement(required = false)
protected List<DdlScript> after;
@XmlAttribute(name = "columnName", required = true)
protected String columnName;
@XmlAttribute(name = "tableName", required = true)
@@ -533,4 +544,17 @@ public class AlterColumn {
this.dropForeignKeyIndex = value;
}
public List<DdlScript> getBefore() {
if (before == null) {
before = new ArrayList<>();
}
return before;
}
public List<DdlScript> getAfter() {
if (after == null) {
after = new ArrayList<>();
}
return after;
}
}
@@ -1,11 +1,14 @@
package io.ebean.dbmigration.migration;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
@@ -39,13 +42,16 @@ import javax.xml.bind.annotation.XmlValue;
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"content"
"before", "after"
})
@XmlRootElement(name = "column")
public class Column {
@XmlValue
protected String content;
@XmlElement(required = false)
protected List<DdlScript> before;
@XmlElement(required = false)
protected List<DdlScript> after;
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "type", required = true)
@@ -76,27 +82,22 @@ public class Column {
protected String foreignKeyIndex;
@XmlAttribute(name = "comment")
protected String comment;
/**
* Gets the value of the content property.
*
* @return possible object is
* {@link String }
*/
public String getContent() {
return content;
public List<DdlScript> getBefore() {
if (before == null) {
before = new ArrayList<>();
}
return before;
}
/**
* Sets the value of the content property.
*
* @param value allowed object is
* {@link String }
*/
public void setContent(String value) {
this.content = value;
public List<DdlScript> getAfter() {
if (after == null) {
after = new ArrayList<>();
}
return after;
}
/**
* Gets the value of the name property.
*
@@ -0,0 +1,73 @@
package io.ebean.dbmigration.migration;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
/**
* <p>Java class for anonymous complex type.
* <p>
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* TODO @Rob: Can this generated automatically?
* </pre>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"ddl"
})
@XmlRootElement(name = "ddl-script")
public class DdlScript {
@XmlValue
protected List<String> ddl;
@XmlAttribute(name = "platforms")
protected String platforms;
/**
* Gets the value of the value property.
*
* @return possible object is
* {@link String }
*/
public List<String> getDdl() {
if (ddl == null) {
ddl = new ArrayList<>();
}
return ddl;
}
public void setDdl(List<String> ddl) {
this.ddl = ddl;
}
/**
* Gets the value of the platforms property.
*
* @return possible object is
* {@link String }
*/
public String getPlatforms() {
return platforms;
}
/**
* Sets the value of the platforms property.
*
* @param value allowed object is
* {@link String }
*/
public void setPlatforms(String value) {
this.platforms = value;
}
}
@@ -29,6 +29,10 @@ public class DropTable {
@XmlAttribute(name = "name", required = true)
protected String name;
@XmlAttribute(name = "sequenceCol")
protected String sequenceCol;
@XmlAttribute(name = "sequenceName")
protected String sequenceName;
/**
* Gets the value of the name property.
@@ -50,4 +54,44 @@ public class DropTable {
this.name = value;
}
/**
* Gets the value of the sequenceCol property.
*
* @return possible object is
* {@link String }
*/
public String getSequenceCol() {
return sequenceCol;
}
/**
* Gets the value of the sequenceName property.
*
* @return possible object is
* {@link String }
*/
public String getSequenceName() {
return sequenceName;
}
/**
* Sets the value of the sequenceCol property.
*
* @param value allowed object is
* {@link String }
*/
public void setSequenceCol(String value) {
this.sequenceCol = value;
}
/**
* Sets the value of the sequenceName property.
*
* @param value allowed object is
* {@link String }
*/
public void setSequenceName(String value) {
this.sequenceName = value;
}
}
@@ -186,5 +186,13 @@ public class ObjectFactory {
public Migration createMigration() {
return new Migration();
}
/**
* Create an instance of {@link DdlScript }
*/
public DdlScript createDdlScript() {
return new DdlScript();
}
}
@@ -1,7 +1,12 @@
package io.ebean.dbmigration.model;
import java.util.List;
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebean.dbmigration.migration.AlterColumn;
import io.ebean.dbmigration.migration.Column;
import io.ebean.dbmigration.migration.DdlScript;
import io.ebeaninternal.server.deploy.DbMigrationInfo;
/**
* A column in the logical model.
@@ -37,6 +42,8 @@ public class MColumn {
private AlterColumn alterColumn;
private boolean draftOnly;
private List<DbMigrationInfo> dbMigrationInfos;
public MColumn(Column column) {
this.name = column.getName();
@@ -77,6 +84,7 @@ public class MColumn {
copy.checkConstraint = checkConstraint;
copy.checkConstraintName = checkConstraintName;
copy.defaultValue = defaultValue;
copy.dbMigrationInfos = dbMigrationInfos;
copy.references = references;
copy.comment = comment;
copy.foreignKeyName = foreignKeyName;
@@ -256,6 +264,24 @@ public class MColumn {
c.setComment(comment);
c.setUnique(unique);
c.setUniqueOneToOne(uniqueOneToOne);
if (dbMigrationInfos != null) {
for (DbMigrationInfo info : dbMigrationInfos) {
if (!info.getPreAdd().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPreAdd());
script.setPlatforms(info.joinPlatforms());
c.getBefore().add(script);
}
if (!info.getPostAdd().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPostAdd());
script.setPlatforms(info.joinPlatforms());
c.getAfter().add(script);
}
}
}
return c;
}
@@ -280,6 +306,24 @@ public class MColumn {
if (tableWithHistory) {
alterColumn.setWithHistory(Boolean.TRUE);
}
if (dbMigrationInfos != null) {
for (DbMigrationInfo info : dbMigrationInfos) {
if (!info.getPreAlter().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPreAlter());
script.setPlatforms(info.joinPlatforms());
alterColumn.getBefore().add(script);
}
if (!info.getPostAlter().isEmpty()) {
DdlScript script = new DdlScript();
script.setDdl(info.getPostAlter());
script.setPlatforms(info.joinPlatforms());
alterColumn.getAfter().add(script);
}
}
}
}
return alterColumn;
}
@@ -290,6 +334,8 @@ public class MColumn {
*/
public void compare(ModelDiff modelDiff, MTable table, MColumn newColumn) {
this.dbMigrationInfos = newColumn.dbMigrationInfos;
boolean tableWithHistory = table.isWithHistory();
String tableName = table.getName();
@@ -313,14 +359,18 @@ public class MColumn {
if (different(defaultValue, newColumn.defaultValue)) {
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
if (newColumn.defaultValue == null) {
alter.setDefaultValue("DROP DEFAULT");
alter.setDefaultValue(DdlHelp.DROP_DEFAULT);
} else {
alter.setDefaultValue(newColumn.defaultValue);
}
}
if (different(comment, newColumn.comment)) {
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
alter.setComment(newColumn.comment);
if (newColumn.comment == null) {
alter.setComment(DdlHelp.DROP_COMMENT);
} else {
alter.setComment(newColumn.comment);
}
}
if (different(checkConstraint, newColumn.checkConstraint)) {
AlterColumn alter = getAlterColumn(tableName, tableWithHistory);
@@ -378,6 +428,10 @@ public class MColumn {
}
}
public void setDbMigrationInfos(List<DbMigrationInfo> dbMigrationInfos) {
this.dbMigrationInfos = dbMigrationInfos;
}
/**
* Apply changes based on the AlterColumn request.
*/
@@ -388,6 +442,7 @@ public class MColumn {
}
if (hasValue(alterColumn.getDropForeignKey())) {
foreignKeyName = null;
references = null;
}
if (hasValue(alterColumn.getDropForeignKeyIndex())) {
foreignKeyIndex = null;
@@ -405,6 +460,9 @@ public class MColumn {
}
if (hasValue(alterColumn.getDefaultValue())) {
defaultValue = alterColumn.getDefaultValue();
if (DdlHelp.isDropDefault(defaultValue)) {
defaultValue = null;
}
}
if (hasValue(alterColumn.getCheckConstraint())) {
checkConstraint = alterColumn.getCheckConstraint();
@@ -427,6 +485,12 @@ public class MColumn {
if (hasValue(alterColumn.getForeignKeyIndex())) {
foreignKeyIndex = alterColumn.getForeignKeyIndex();
}
if (hasValue(alterColumn.getComment())) {
comment = alterColumn.getComment();
if (DdlHelp.isDropComment(comment)) {
comment = null;
}
}
}
}
@@ -1,5 +1,6 @@
package io.ebean.dbmigration.model;
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebean.dbmigration.migration.AddColumn;
import io.ebean.dbmigration.migration.AddHistoryTable;
import io.ebean.dbmigration.migration.AddTableComment;
@@ -180,6 +181,24 @@ public class MTable {
public DropTable dropTable() {
DropTable dropTable = new DropTable();
dropTable.setName(name);
// we must add pk col name & sequence name, as we have to delete the sequence also.
if (identityType != IdentityType.GENERATOR && identityType != IdentityType.EXTERNAL) {
String pkCol = null;
for (MColumn column : columns.values()) {
if (column.isPrimaryKey()) {
if (pkCol == null) {
pkCol = column.getName();
} else { // multiple pk cols -> no sequence
pkCol = null;
break;
}
}
}
if (pkCol != null) {
dropTable.setSequenceCol(pkCol);
dropTable.setSequenceName(sequenceName);
}
}
return dropTable;
}
@@ -288,7 +307,11 @@ public class MTable {
if (MColumn.different(comment, newTable.comment)) {
AddTableComment addTableComment = new AddTableComment();
addTableComment.setName(name);
addTableComment.setComment(newTable.comment);
if (newTable.comment == null) {
addTableComment.setComment(DdlHelp.DROP_COMMENT);
} else {
addTableComment.setComment(newTable.comment);
}
modelDiff.addTableComment(addTableComment);
}
}
@@ -1,7 +1,9 @@
package io.ebean.dbmigration.model;
import io.ebean.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebean.dbmigration.migration.AddColumn;
import io.ebean.dbmigration.migration.AddHistoryTable;
import io.ebean.dbmigration.migration.AddTableComment;
import io.ebean.dbmigration.migration.AlterColumn;
import io.ebean.dbmigration.migration.ChangeSet;
import io.ebean.dbmigration.migration.ChangeSetType;
@@ -132,6 +134,10 @@ public class ModelContainer {
applyChange((AddHistoryTable) change);
} else if (change instanceof DropHistoryTable) {
applyChange((DropHistoryTable) change);
} else if (change instanceof AddTableComment) {
applyChange((AddTableComment) change);
} else {
throw new IllegalArgumentException("No rule for " + change);
}
}
}
@@ -159,6 +165,18 @@ public class ModelContainer {
}
table.setWithHistory(false);
}
private void applyChange(AddTableComment change) {
MTable table = tables.get(change.getName());
if (table == null) {
throw new IllegalStateException("Table [" + change.getName() + "] does not exist in model?");
}
if (DdlHelp.isDropComment(change.getComment())) {
table.setComment(null);
} else {
table.setComment(change.getComment());
}
}
/**
* Apply a CreateTable change to the model.
@@ -177,7 +177,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
String refColumn = importedProperty.getDbColumn();
MColumn col = table.addColumn(dbCol, columnDefn, !p.isNullable());
col.setDbMigrationInfos(p.getDbMigrationInfos());
col.setDefaultValue(p.getDbColumnDefault());
if (columns.length == 1) {
// single references column (put it on the column)
String refTable = importedProperty.getBeanDescriptor().getBaseTable();
@@ -232,6 +233,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
col.setDbMigrationInfos(p.getDbMigrationInfos());
if (!p.isNullable() || p.isDDLNotNull()) {
col.setNotnull(true);
}
@@ -1,9 +1,5 @@
package io.ebean.event.changelog;
import io.ebean.ValuePair;
import java.util.Map;
/**
* A bean insert, update or delete change sent as part of a ChangeSet.
*/
@@ -12,68 +8,68 @@ public class BeanChange {
/**
* The underling base table name.
*/
String table;
private final String type;
/**
* The tenantId value.
*/
Object tenantId;
private final Object tenantId;
/**
* The id value.
*/
Object id;
private final Object id;
/**
* The INSERT, UPDATE or DELETE change type.
*/
ChangeType type;
private final ChangeType event;
/**
* The time the bean change was created.
*/
long eventTime;
private final long eventTime;
/**
* The values for insert or update. Note that null values are not included for insert.
* The change in JSON form.
*/
Map<String, ValuePair> values;
private final String data;
/**
* Construct with all the details.
* The change in JSON form.
*/
public BeanChange(String table, Object tenantId, Object id, ChangeType type, Map<String, ValuePair> values) {
this.table = table;
private final String oldData;
/**
* Construct with change as JSON.
*/
public BeanChange(String type, Object tenantId, Object id, ChangeType event, String data, String oldData) {
this.type = type;
this.tenantId = tenantId;
this.id = id;
this.type = type;
this.event = event;
this.eventTime = System.currentTimeMillis();
this.values = values;
this.data = data;
this.oldData = oldData;
}
/**
* Default constructor for JSON tools.
* Construct with change as JSON.
*/
public BeanChange() {
public BeanChange(String table, Object tenantId, Object id, ChangeType event, String data) {
this(table, tenantId , id , event , data , null);
}
@Override
public String toString() {
return "table:" + table + " tenantId: " + tenantId + " id:" + id + " values:" + values;
return "type:" + type + " tenantId: " + tenantId + " id:" + id + " data:" + data;
}
/**
* Return the object type (typically table name).
*/
public String getTable() {
return table;
}
/**
* Set the object type (for JSON tools).
*/
public void setTable(String table) {
this.table = table;
public String getType() {
return type;
}
/**
@@ -83,13 +79,6 @@ public class BeanChange {
return tenantId;
}
/**
* Set the bean id (for JSON tools).
*/
public void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
/**
* Return the object id.
*/
@@ -97,25 +86,11 @@ public class BeanChange {
return id;
}
/**
* Set the bean id (for JSON tools).
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the change type (INSERT, UPDATE or DELETE).
*/
public ChangeType getType() {
return type;
}
/**
* Set the type (for JSON tools).
*/
public void setType(ChangeType type) {
this.type = type;
public ChangeType getEvent() {
return event;
}
/**
@@ -126,23 +101,16 @@ public class BeanChange {
}
/**
* Set the event time in epoch millis.
* Return the change data in JSON form.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
public String getData() {
return data;
}
/**
* Return the value pairs. For inserts the ValuePair oldValue is always null.
* Return the old data in JSON form.
*/
public Map<String, ValuePair> getValues() {
return values;
}
/**
* Set the value pairs (for JSON tools).
*/
public void setValues(Map<String, ValuePair> values) {
this.values = values;
public String getOldData() {
return oldData;
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
.fetch("shippingAddress");
.setId(7)
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
+1 -1
View File
@@ -28,7 +28,7 @@ Customer customer =
.setId(7)
.fetch("billingAddress")
.fetch("shippingAddress")
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
+9 -9
View File
@@ -96,7 +96,7 @@ public class EJson {
/**
* Parse the json and return as a modify aware List.
*/
public static List<Object> parseList(String json, boolean modifyAware) throws IOException {
public static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseList(json, modifyAware);
}
@@ -125,8 +125,8 @@ public class EJson {
* Parse the json returning as a List taking into account the current token.
*/
@SuppressWarnings("unchecked")
public static List<Object> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return (List<Object>) EJsonReader.parse(parser, currentToken, false);
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return (List<T>) EJsonReader.parse(parser, currentToken, false);
}
/**
@@ -153,23 +153,23 @@ public class EJson {
/**
* Parse the json returning a Set that might be modify aware.
*/
public static Set parseSet(String json, boolean modifyAware) throws IOException {
List<Object> list = parseList(json, modifyAware);
public static <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
List<T> list = parseList(json, modifyAware);
if (list == null) {
return null;
}
if (modifyAware) {
return ((ModifyAwareList) list).asSet();
return ((ModifyAwareList<T>) list).asSet();
} else {
return new LinkedHashSet<>(list);
return new LinkedHashSet<T>(list);
}
}
/**
* Parse the json returning as a Set taking into account the current token.
*/
public static Set<Object> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<T>(parseList(parser, currentToken));
}
}
@@ -52,8 +52,8 @@ class EJsonReader {
}
@SuppressWarnings("unchecked")
static List<Object> parseList(String json, boolean modifyAware) throws IOException {
return (List<Object>) parse(json, modifyAware);
static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return (List<T>) parse(json, modifyAware);
}
@SuppressWarnings("unchecked")
@@ -3,6 +3,7 @@ package io.ebean.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility String class that supports String manipulation functions.
@@ -12,6 +13,10 @@ public class StringHelper {
private static final char SINGLE_QUOTE = '\'';
private static final char DOUBLE_QUOTE = '"';
private static final Pattern SPLIT_NAMES = Pattern.compile("[\\s,;]+");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* parses a String of the form name1='value1' name2='value2'. Note that you
@@ -552,4 +557,28 @@ public class StringHelper {
return false;
}
/**
* Splits at any whitespace "," or ";" and trims the result.
* It does not return empty entries.
*/
public static String[] splitNames(String names) {
if (names == null || names.isEmpty()) {
return EMPTY_STRING_ARRAY;
}
String[] result = SPLIT_NAMES.split(names);
if (result.length == 0) {
return EMPTY_STRING_ARRAY; // don't know if this ever can happen
}
if ("".equals(result[0])) { // = input string starts with whitespace
if (result.length == 1) { // = input string contains only whitespace
return EMPTY_STRING_ARRAY;
} else {
String ret[] = new String[result.length-1]; // remove first entry
System.arraycopy(result, 1, ret, 0, ret.length);
return ret;
}
} else {
return result;
}
}
}
@@ -35,7 +35,7 @@ public class BindParams implements Serializable {
* Bind hash and count used to detect when the bind values have changed such
* that the generated SQL (with named parameters) needs to be recalculated.
*/
private int[] bindHash;
private String bindHash;
public BindParams() {
}
@@ -56,32 +56,29 @@ public class BindParams implements Serializable {
* taken into account when calculating the query hash.
* </p>
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
int[] vals = calcQueryPlanHash();
builder.add(vals[0]).bind(vals[1]);
public String calcQueryPlanHash() {
StringBuilder builder = new StringBuilder();
buildQueryPlanHash(builder);
return builder.toString();
}
/**
* Calculate and return a query plan bind hash with total bind count.
*/
public int[] calcQueryPlanHash() {
public void buildQueryPlanHash(StringBuilder builder) {
int tempBindCount;
int bc = 0;
int hc = 92821;
for (Param param : positionedParameters) {
tempBindCount = param.queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + tempBindCount;
builder.append("p").append(bc).append(" ?:").append(tempBindCount).append(",");
}
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
tempBindCount = entry.getValue().queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + entry.getKey().hashCode();
hc = hc * 92821 + tempBindCount;
builder.append("n").append(bc).append(" k:").append(entry.getKey()).append(" ?:").append(tempBindCount).append(",");
}
return new int[]{hc, bc};
}
/**
@@ -277,9 +274,9 @@ public class BindParams implements Serializable {
bindHash = calcQueryPlanHash();
return false;
}
int[] oldPlan = bindHash;
String oldPlan = bindHash;
bindHash = calcQueryPlanHash();
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
return bindHash.equals(oldPlan);
}
/**
@@ -1,7 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.TxScope;
import io.ebean.PersistBatch;
import io.ebean.annotation.PersistBatch;
import java.util.ArrayList;
@@ -53,6 +53,8 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private Boolean restoreBatchGeneratedKeys;
private boolean restoreBatchFlushOnQuery;
/**
* Flag set when a rollback has occurred.
*/
@@ -77,10 +79,14 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isBatchFlushOnQuery();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
}
if (!txScope.isFlushOnQuery()) {
transaction.setBatchFlushOnQuery(false);
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
}
@@ -158,6 +164,7 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
if (created) {
transaction.commit();
} else {
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.PersistBatch;
import io.ebean.annotation.PersistBatch;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
@@ -0,0 +1,18 @@
package io.ebeaninternal.api;
/**
* Plugin API available to invoke something prior to container bootup.
* <p>
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
* we have a test DB docker container running and setup ready to go.
* </p>
*/
public interface SpiContainerBootup {
/**
* Run something at bootup prior to the container starting.
*
* For example, start DB docker container(s).
*/
void bootup();
}
@@ -25,6 +25,11 @@ import java.util.List;
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the server extended Json context.
*/
SpiJsonContext jsonExtended();
/**
* For internal use, shutdown of the server invoked by JVM Shutdown.
*/
@@ -200,4 +205,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
*/
DataTimeZone getDataTimeZone();
/**
* Check for slow query event.
*/
void slowQueryCheck(long executionTimeMicros, int rowCount, SpiQuery<?> query);
}
@@ -48,18 +48,13 @@ public interface SpiExpression extends Expression {
* from an AutoTune perspective and get different tuning.
* </p>
*/
void queryPlanHash(HashQueryPlanBuilder builder);
void queryPlanHash(StringBuilder builder);
/**
* Return the hash value for the values that will be bound.
*/
int queryBindHash();
/**
* Return true if the expression is the same without taking into account bind values.
*/
boolean isSameByPlan(SpiExpression other);
/**
* Return true if the expression is the same with respect to bind values.
*/
@@ -0,0 +1,24 @@
package io.ebeaninternal.api;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.text.json.JsonContext;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.Writer;
/**
* Extended Json Context for internal server use.
*/
public interface SpiJsonContext extends JsonContext {
/**
* Create a Json Writer for writing beans as JSON.
*/
SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options);
/**
* Create a Json Writer for writing beans as JSON supplying a writer.
*/
SpiJsonWriter createJsonWriter(Writer writer);
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.CacheMode;
import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
@@ -583,9 +584,9 @@ public interface SpiQuery<T> extends Query<T> {
boolean isUseBeanCache();
/**
* Return true if this query should use/check the query cache.
* Return the cache mode if this query should use/check the query cache.
*/
boolean isUseQueryCache();
CacheMode getUseQueryCache();
/**
* Return true if the beans from this query should be loaded into the bean
@@ -1,9 +1,10 @@
package io.ebeaninternal.extraddl.model;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.ebean.util.StringHelper;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
@@ -16,8 +17,6 @@ public class ExtraDdlXmlReader {
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
private static final Pattern PLATFORM_REGEX_SPLIT = Pattern.compile("[,;]");
/**
* Return the combined extra DDL that should be run given the platform name.
*/
@@ -48,9 +47,8 @@ public class ExtraDdlXmlReader {
return true;
}
String[] names = PLATFORM_REGEX_SPLIT.split(platforms);
for (String name : names) {
if (name.trim().toLowerCase().contains(platformName)) {
for (String name : StringHelper.splitNames(platforms)) {
if (name.toLowerCase().contains(platformName)) {
return true;
}
}
@@ -7,6 +7,7 @@ import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import java.util.concurrent.ConcurrentHashMap;
@@ -82,7 +83,7 @@ class DefaultCacheHolder {
}
private ServerCacheOptions getQueryOptions(Class<?> cls) {
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
CacheQueryTuning tuning = AnnotationBase.findAnnotation(cls, CacheQueryTuning.class);
if (tuning != null) {
return new ServerCacheOptions(tuning).applyDefaults(queryDefault);
}
@@ -1,12 +1,11 @@
package io.ebeaninternal.server.changelog;
import io.ebean.ValuePair;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.event.changelog.ChangeType;
import io.ebean.text.json.JsonContext;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import java.io.IOException;
import java.io.Writer;
@@ -15,25 +14,23 @@ import java.util.Map;
/**
* Builds JSON document for a bean change.
*/
public class ChangeJsonBuilder {
class ChangeJsonBuilder {
protected final JsonFactory jsonFactory = new JsonFactory();
protected final JsonContext json;
protected ChangeJsonBuilder(JsonContext json) {
ChangeJsonBuilder(JsonContext json) {
this.json = json;
}
/**
* Write the bean change as JSON.
*/
public void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet, int position) throws IOException {
void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet) throws IOException {
try (JsonGenerator generator = jsonFactory.createGenerator(writer)) {
writeBeanChange(generator, bean, changeSet, position);
writeBeanChange(generator, bean, changeSet);
generator.flush();
}
}
@@ -41,34 +38,29 @@ public class ChangeJsonBuilder {
/**
* Write the bean change as JSON document containing the transaction header details.
*/
protected void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet, int position) throws IOException {
private void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet) throws IOException {
gen.writeStartObject();
writeBeanTransactionDetails(gen, changeSet, position);
gen.writeStringField("object", bean.getTable());
gen.writeNumberField("ts", bean.getEventTime());
gen.writeStringField("change", bean.getEvent().getCode());
gen.writeStringField("type", bean.getType());
gen.writeStringField("id", bean.getId().toString());
if (bean.getTenantId() != null) {
gen.writeStringField("tenantId", bean.getTenantId().toString());
}
gen.writeStringField("objectId", bean.getId().toString());
gen.writeStringField("change", bean.getType().getCode());
gen.writeNumberField("eventTime", bean.getEventTime());
writeBeanTransactionDetails(gen, changeSet);
writeBeanValues(gen, bean);
gen.writeEndObject();
}
/**
* Denormalise by writing the transaction header details.
*/
protected void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet, int position) throws IOException {
private void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet) throws IOException {
gen.writeStringField("txnId", changeSet.getTxnId());
gen.writeStringField("txnState", changeSet.getTxnState().getCode());
gen.writeNumberField("txnBatch", changeSet.getTxnBatch());
gen.writeNumberField("txnPosition", position);
String source = changeSet.getSource();
if (source != null) {
gen.writeStringField("source", source);
@@ -94,39 +86,18 @@ public class ChangeJsonBuilder {
/**
* For insert and update write the new/old values.
*/
protected void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
private void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
if (bean.getType() != ChangeType.DELETE) {
gen.writeFieldName("values");
gen.writeStartObject();
writeValuePairs(bean, gen);
gen.writeEndObject();
}
}
if (bean.getEvent() != ChangeType.DELETE) {
gen.writeFieldName("data");
gen.writeRaw(":");
gen.writeRaw(bean.getData());
/**
* Write all the value pairs suppressing null values.
* <p>
* We are intentionally keeping the same new/old structure for both inserts and updates.
* </p>
*/
protected void writeValuePairs(BeanChange bean, JsonGenerator gen) throws IOException {
for (Map.Entry<String, ValuePair> entry : bean.getValues().entrySet()) {
gen.writeFieldName(entry.getKey());
gen.writeStartObject();
ValuePair value = entry.getValue();
Object newValue = value.getNewValue();
if (newValue != null) {
gen.writeFieldName("new");
json.writeScalar(gen, newValue);
String oldData = bean.getOldData();
if (oldData != null) {
gen.writeRaw(",\"oldData\":");
gen.writeRaw(oldData);
}
Object oldValue = value.getOldValue();
if (oldValue != null) {
gen.writeFieldName("old");
json.writeScalar(gen, oldValue);
}
gen.writeEndObject();
}
}
@@ -10,15 +10,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.StringWriter;
import java.util.List;
import java.util.Properties;
/**
* Logs the change sets in JSON to logger named <code>io.ebean.ChangeLog</code>.
* <p>
* The logged entries duplicate/denormalise the transaction details so that each bean change
* is fully contained with the transaction information.
* </p>
* Simply logs the change sets in JSON form to logger named <code>io.ebean.ChangeLog</code>.
*/
public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
@@ -30,22 +25,17 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
/**
* The named logger we send the change set payload to. Can be externally configured as desired.
*/
protected static final Logger changeLog = LoggerFactory.getLogger("io.ebean.ChangeLog");
private static final Logger changeLog = LoggerFactory.getLogger("io.ebean.ChangeLog");
/**
* Used to build the JSON.
*/
protected ChangeJsonBuilder jsonBuilder;
private ChangeJsonBuilder jsonBuilder;
/**
* A bigger default buffer for bean inserts and updates (that have value pairs).
*/
protected int defaultBufferSize = 400;
/**
* Expected to be a reasonable buffer size for deletes (which do not have value pairs).
*/
protected int defaultDeleteBufferSize = 250;
private int defaultBufferSize = 400;
public DefaultChangeLogListener() {
}
@@ -79,13 +69,11 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
@Override
public void log(ChangeSet changeSet) {
List<BeanChange> changes = changeSet.getChanges();
for (int i = 0; i < changes.size(); i++) {
for (BeanChange beanChange : changeSet.getChanges()) {
// log each bean change as a separate log entry
BeanChange beanChange = changes.get(i);
try {
StringWriter writer = new StringWriter(getBufferSize(beanChange));
jsonBuilder.writeBeanJson(writer, beanChange, changeSet, i);
jsonBuilder.writeBeanJson(writer, beanChange, changeSet);
changeLog.info(writer.toString());
} catch (Exception e) {
logger.error("Exception logging beanChange " + beanChange.toString(), e);
@@ -96,9 +84,9 @@ public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
/**
* Return a decent buffer size based on the bean change.
*/
protected int getBufferSize(BeanChange beanChange) {
private int getBufferSize(BeanChange beanChange) {
return ChangeType.DELETE == beanChange.getType() ? defaultDeleteBufferSize : defaultBufferSize;
return ChangeType.DELETE == beanChange.getEvent() ? 250 : defaultBufferSize;
}
}
@@ -52,7 +52,7 @@ public class DefaultChangeLogRegister implements ChangeLogRegister {
* Find and return the ChangeLog annotation in the inheritance hierarchy.
*/
private ChangeLog getChangeLog(Class<?> beanType) {
return AnnotationBase.findAnnotation(beanType, ChangeLog.class);
return AnnotationBase.findAnnotationRecursive(beanType, ChangeLog.class);
}
/**
@@ -134,8 +134,6 @@ public class DatabasePlatformFactory {
String dbProductName = metaData.getDatabaseProductName();
dbProductName = dbProductName.toLowerCase();
int majorVersion = metaData.getDatabaseMajorVersion();
if (dbProductName.contains("oracle")) {
return new OraclePlatform();
} else if (dbProductName.contains("microsoft")) {
@@ -3,7 +3,9 @@ package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.server.lib.DaemonExecutorService;
import io.ebeaninternal.server.lib.DaemonScheduleThreadPool;
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
@@ -28,12 +30,38 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
*/
@Override
public void execute(Runnable r) {
pool.execute(r);
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
pool.execute(r);
} else {
pool.execute(() -> {
MDC.setContextMap(map);
try {
r.run();
} finally {
MDC.clear();
}
});
}
}
@Override
public void executePeriodically(Runnable r, long delay, TimeUnit unit) {
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
} else {
schedulePool.scheduleWithFixedDelay(() -> {
MDC.setContextMap(map);
try {
r.run();
} finally {
MDC.clear();
}
}, delay, delay, unit);
}
}
@Override
@@ -173,7 +173,7 @@ public class DefaultBeanLoader {
query.setReadOnly(true);
}
server.findUnique(query, t);
server.findOne(query, t);
if (beanCollection != null) {
if (beanCollection.checkEmptyLazyLoad()) {
@@ -316,7 +316,7 @@ public class DefaultBeanLoader {
query.select("*");
}
Object dbBean = query.findUnique();
Object dbBean = query.findOne();
if (dbBean == null) {
String msg = "Bean not found during lazy load or refresh." + " id[" + id + "] type[" + desc.getBeanType() + "]";
throw new EntityNotFoundException(msg);
@@ -14,6 +14,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.dbmigration.DbOffline;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiContainerBootup;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
@@ -51,6 +52,8 @@ public class DefaultContainer implements SpiContainer {
public DefaultContainer(ContainerConfig containerConfig) {
invokeBootupPlugin();
this.clusterManager = new ClusterManager(containerConfig);
this.jndiDataSourceFactory = new JndiDataSourceLookup();
@@ -59,6 +62,13 @@ public class DefaultContainer implements SpiContainer {
ShutdownManager.registerContainer(this);
}
private void invokeBootupPlugin() {
Iterator<SpiContainerBootup> it = ServiceLoader.load(SpiContainerBootup.class).iterator();
while (it.hasNext()) {
it.next().bootup();
}
}
@Override
public void shutdown() {
clusterManager.shutdown();
@@ -20,8 +20,6 @@ import io.ebean.SqlRow;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.TransactionCallback;
import io.ebean.TxCallable;
import io.ebean.TxRunnable;
import io.ebean.TxScope;
import io.ebean.Update;
import io.ebean.UpdateQuery;
@@ -35,6 +33,7 @@ import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.ServerConfig;
@@ -42,6 +41,8 @@ import io.ebean.config.TenantMode;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.dbmigration.DdlGenerator;
import io.ebean.event.BeanPersistController;
import io.ebean.config.SlowQueryEvent;
import io.ebean.config.SlowQueryListener;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
@@ -50,16 +51,8 @@ import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import io.ebeaninternal.api.LoadBeanRequest;
import io.ebeaninternal.api.LoadManyRequest;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.*;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -90,8 +83,8 @@ import io.ebeaninternal.server.transaction.TransactionScopeManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebean.TxIsolation;
import io.ebean.TxType;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -99,6 +92,7 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@@ -106,6 +100,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -176,7 +171,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final EncryptKeyManager encryptKeyManager;
private final JsonContext jsonContext;
private final SpiJsonContext jsonContext;
private final DocumentStore documentStore;
@@ -210,6 +205,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final boolean collectQueryStatsByNode;
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
/**
* Cache used to collect statistics based on ObjectGraphNode (used to highlight lazy loading origin points).
*/
@@ -235,6 +234,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.defaultPersistenceContextScope = serverConfig.getPersistenceContextScope();
this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
this.slowQueryMicros = config.getSlowQueryMicros();
this.slowQueryListener = config.getSlowQueryListener();
this.beanDescriptorManager = config.getBeanDescriptorManager();
beanDescriptorManager.setEbeanServer(this);
@@ -624,7 +625,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
return find(type).select(idProp.getName()).setId(id).findUnique();
return find(type).select(idProp.getName()).setId(id).findOne();
}
@Override
@@ -659,12 +660,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public <T> T execute(TxCallable<T> c) {
return execute(null, c);
public <T> T executeCall(Callable<T> c) {
return executeCall(null, c);
}
@Override
public <T> T execute(TxScope scope, TxCallable<T> c) {
public <T> T executeCall(TxScope scope, Callable<T> c) {
ScopeTrans scopeTrans = createScopeTrans(scope);
try {
return c.call();
@@ -672,8 +673,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
} catch (Error e) {
throw scopeTrans.caughtError(e);
} catch (RuntimeException e) {
throw scopeTrans.caughtThrowable(e);
} catch (Exception e) {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
} finally {
scopeTrans.onFinally();
@@ -681,12 +682,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public void execute(TxRunnable r) {
public void execute(Runnable r) {
execute(null, r);
}
@Override
public void execute(TxScope scope, TxRunnable r) {
public void execute(TxScope scope, Runnable r) {
ScopeTrans scopeTrans = createScopeTrans(scope);
try {
r.run();
@@ -694,8 +695,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
} catch (Error e) {
throw scopeTrans.caughtError(e);
} catch (RuntimeException e) {
throw scopeTrans.caughtThrowable(e);
} catch (Exception e) {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
} finally {
scopeTrans.onFinally();
@@ -1034,17 +1035,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public SqlQuery createSqlQuery(String sql) {
return new DefaultRelationalQuery(this, sql);
return new DefaultRelationalQuery(this, sql.trim());
}
@Override
public SqlUpdate createSqlUpdate(String sql) {
return new DefaultSqlUpdate(this, sql);
return new DefaultSqlUpdate(this, sql.trim());
}
@Override
public CallableSql createCallableSql(String sql) {
return new DefaultCallableSql(this, sql);
return new DefaultCallableSql(this, sql.trim());
}
@Override
@@ -1179,11 +1180,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> T findUnique(Query<T> query, Transaction transaction) {
return findOne(query, transaction);
}
@Override
public <T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction) {
return Optional.ofNullable(findOne(query, transaction));
@@ -1319,10 +1315,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return findIdsWithCopy(((SpiQuery<T>) query).copy(), t);
}
@SuppressWarnings("unchecked")
@Override
public <A, T> List<A> findIdsWithCopy(Query<T> query, Transaction t) {
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
return new CopyOnFirstWriteList<>((List<A>) result);
} else {
return (List<A>) result;
}
}
try {
request.initTransIfRequired();
return request.findIds();
@@ -1510,11 +1515,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public SqlRow findUnique(SqlQuery query, Transaction t) {
return findOne(query, t);
}
@Override
public SqlRow findOne(SqlQuery query, Transaction t) {
@@ -2207,6 +2207,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return jsonContext;
}
@Override
public SpiJsonContext jsonExtended() {
return jsonContext;
}
@Override
public void collectQueryStats(ObjectGraphNode node, long loadedBeanCount, long timeMicros) {
@@ -2222,4 +2227,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
if (timeMicros > slowQueryMicros) {
if (slowQueryListener != null) {
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
}
}
}
}
@@ -0,0 +1,26 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.SlowQueryEvent;
import io.ebean.config.SlowQueryListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Default slow query listener implementation that logs a warning message.
*/
class DefaultSlowQueryListener implements SlowQueryListener {
private static final Logger log = LoggerFactory.getLogger("io.ebean.SlowQuery");
@Override
public void process(SlowQueryEvent event) {
String firstStack = "";
ObjectGraphNode node = event.getOriginNode();
if (node != null) {
firstStack = node.getOriginQueryPoint().getFirstStackElement();
}
log.warn("Slow query warning - millis:{} rows:{} caller[{}] sql[{}]", event.getTimeMillis(), event.getRowCount(), firstStack, event.getSql());
}
}
@@ -3,12 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -54,51 +49,4 @@ public class DiffHelp {
return desc.diff((EntityBean) newBean, (EntityBean) oldBean);
}
/**
* Flattens an existing diff map converting assoc one beans into the associated id changes.
*/
public static Map<String, ValuePair> flatten(Map<String, ValuePair> values, BeanDescriptor<?> desc) {
Map<String, ValuePair> flattened = null;
Iterator<Map.Entry<String, ValuePair>> iterator = values.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ValuePair> entry = iterator.next();
BeanProperty beanProperty = desc.getBeanProperty(entry.getKey());
if (beanProperty instanceof BeanPropertyAssocMany) {
// filter out assoc many bean properties
iterator.remove();
} else if (beanProperty instanceof BeanPropertyAssocOne) {
BeanPropertyAssocOne<?> assoc = (BeanPropertyAssocOne<?>) beanProperty;
if (!assoc.isEmbedded()) {
// flatten for assoc one beans
if (flattened == null) {
flattened = new LinkedHashMap<>();
}
flattenToId(flattened, entry, beanProperty, assoc);
iterator.remove();
}
}
}
if (flattened != null) {
values.putAll(flattened);
}
return values;
}
private static void flattenToId(Map<String, ValuePair> flattened, Map.Entry<String, ValuePair> entry, BeanProperty beanProperty, BeanPropertyAssocOne<?> assoc) {
BeanDescriptor<?> oneDesc = assoc.getTargetDescriptor();
ValuePair value = entry.getValue();
Object newId = value.getNewValue() == null ? null : oneDesc.getId((EntityBean) value.getNewValue());
Object oldId = value.getOldValue() == null ? null : oneDesc.getId((EntityBean) value.getOldValue());
String propName = beanProperty.getName() + "." + oneDesc.getIdProperty().getName();
flattened.put(propName, new ValuePair(newId, oldId));
}
}
@@ -1,12 +1,14 @@
package io.ebeaninternal.server.core;
import com.fasterxml.jackson.core.JsonFactory;
import io.ebean.ExpressionFactory;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.SlowQueryListener;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
@@ -14,9 +16,9 @@ import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebean.text.json.JsonContext;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
import io.ebeaninternal.server.cache.DefaultCacheAdapter;
@@ -60,7 +62,6 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import com.fasterxml.jackson.core.JsonFactory;
import org.avaje.datasource.DataSourcePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -261,7 +262,7 @@ public class InternalConfiguration {
}
}
public JsonContext createJsonContext(SpiEbeanServer server) {
public SpiJsonContext createJsonContext(SpiEbeanServer server) {
return new DJsonContext(server, jsonFactory, typeManager);
}
@@ -432,4 +433,30 @@ public class InternalConfiguration {
public ServerCacheManager cache() {
return new DefaultCacheAdapter(cacheManager);
}
/**
* Return the slow query warning limit in micros.
*/
long getSlowQueryMicros() {
long millis = serverConfig.getSlowQueryMillis();
return (millis < 1) ? Long.MAX_VALUE : millis * 1000L;
}
/**
* Return the SlowQueryListener with a default that logs a warning message.
*/
SlowQueryListener getSlowQueryListener() {
long millis = serverConfig.getSlowQueryMillis();
if (millis < 1) {
return null;
}
SlowQueryListener listener = serverConfig.getSlowQueryListener();
if (listener == null) {
listener = serverConfig.service(SlowQueryListener.class);
if (listener == null) {
listener = new DefaultSlowQueryListener();
}
}
return listener;
}
}
@@ -1,11 +1,13 @@
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.BeanQueryRequest;
@@ -32,6 +34,8 @@ import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -381,7 +385,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
*/
@Override
@SuppressWarnings("unchecked")
public Set<?> findSet() {
public Set<T> findSet() {
return (Set<T>) queryEngine.findMany(this);
}
@@ -389,7 +393,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* Execute the query as findMap.
*/
@Override
public Map<?, ?> findMap() {
@SuppressWarnings("unchecked")
public <K> Map<K, T> findMap() {
String mapKey = query.getMapKey();
if (mapKey == null) {
BeanProperty idProp = beanDescriptor.getIdProperty();
@@ -399,7 +404,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
throw new PersistenceException("No mapKey specified for query");
}
}
return (Map<?, ?>) queryEngine.findMany(this);
return (Map<K, T>) queryEngine.findMany(this);
}
/**
@@ -471,11 +476,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
@SuppressWarnings("unchecked")
public Object getFromQueryCache() {
if (!query.isUseQueryCache()) {
if (query.getUseQueryCache() == CacheMode.OFF) {
return null;
} else {
cacheKey = query.queryHash();
}
cacheKey = query.queryHash();
if (!query.getUseQueryCache().isGet()) {
return null;
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
@@ -491,6 +500,18 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
}
}
if (Boolean.FALSE.equals(query.isReadOnly())) {
// return shallow copies if readonly is explicitly set to false
if (cached instanceof BeanCollection) {
cached = ((BeanCollection<?>)cached).getShallowCopy();
} else if (cached instanceof List) {
cached = new CopyOnFirstWriteList<>((List<?>)cached);
} else if (cached instanceof Set) {
cached = new LinkedHashSet<>((Set<?>)cached);
} else if (cached instanceof Map) {
cached = new LinkedHashMap<>((Map<?,?>)cached);
}
}
return cached;
}
@@ -576,4 +597,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public Object getTenantId() {
return (transaction == null) ? null : transaction.getTenantId();
}
/**
* Check for slow query event.
*/
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
}
}
@@ -18,6 +18,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
@@ -179,6 +180,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.setDraftDirty(entityBean, true);
}
this.dirty = intercept.isDirty();
initGeneratedProperties();
}
/**
@@ -225,6 +227,52 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
persistCascade = transaction.isPersistCascade();
}
private void initGeneratedProperties() {
switch (type) {
case INSERT:
onInsertGeneratedProperties();
break;
case UPDATE:
if (!beanDescriptor.isReference(intercept) && (dirty || statelessUpdate)) {
onUpdateGeneratedProperties();
}
break;
case SOFT_DELETE:
onUpdateGeneratedProperties();
break;
}
}
private void onUpdateGeneratedProperties() {
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
GeneratedProperty generatedProperty = prop.getGeneratedProperty();
if (prop.isVersion()) {
if (isLoadedProperty(prop)) {
// @Version property must be loaded to be involved
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
Object oldVal = prop.getValue(entityBean);
setVersionValue(value);
intercept.setOldValue(prop.getPropertyIndex(), oldVal);
}
} else {
// @WhenModified set without invoking interception
Object oldVal = prop.getValue(entityBean);
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
prop.setValueChanged(entityBean, value);
intercept.setOldValue(prop.getPropertyIndex(), oldVal);
}
}
}
private void onInsertGeneratedProperties() {
for (BeanProperty prop : beanDescriptor.propertiesGenInsert()) {
Object value = prop.getGeneratedProperty().getInsertValue(prop, entityBean, now());
prop.setValueChanged(entityBean, value);
}
}
/**
* If using batch on cascade flush if required.
*/
@@ -525,7 +573,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Prepare the update after potential modifications in a BeanPersistController.
*/
private void postControllerPrepareUpdate() {
if (intercept.isNew() && controller != null) {
if (statelessUpdate && controller != null) {
// 'stateless update' - set dirty properties modified in controller preUpdate
intercept.setNewBeanForUpdate();
}
@@ -780,17 +828,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public void postExecute() {
changeLog();
if (controller != null) {
controllerPost();
}
setNotifyCache();
if (type == Type.UPDATE && (notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE && (isChangeLog || notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
// get the dirty properties for update notification to the doc store
dirtyProperties = intercept.getDirtyProperties();
}
if (isChangeLog) {
changeLog();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
if (isInsert()) {
@@ -857,27 +908,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Determine the concurrency mode depending on fully/partially populated bean.
* <p>
* Specifically with version concurrency we want to check that the version property was one of the
* loaded properties.
* </p>
*/
public ConcurrencyMode determineConcurrencyMode() {
// 'partial bean' update/delete...
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
// check the version property was loaded
BeanProperty prop = beanDescriptor.getVersionProperty();
if (prop == null || !intercept.isLoadedProperty(prop.getPropertyIndex())) {
concurrencyMode = ConcurrencyMode.NONE;
}
}
return concurrencyMode;
}
/**
* Return true if the update DML/SQL must be dynamically generated.
* <p>
@@ -908,11 +938,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void postInsert() {
// mark all properties as loaded after an insert to support immediate update
int len = intercept.getPropertyLength();
for (int i = 0; i < len; i++) {
intercept.setLoadedProperty(i);
}
beanDescriptor.setEmbeddedOwner(entityBean);
beanDescriptor.setAllLoaded(entityBean);
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
@@ -1147,4 +1173,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return now;
}
/**
* Return true if this is a stateless update request (in which case it doesn't really have 'old values').
*/
public boolean isStatelessUpdate() {
return statelessUpdate;
}
}
@@ -111,6 +111,8 @@ public final class PersistRequestUpdateSql extends PersistRequest {
case SQL_DELETE:
transaction.getEvent().add(tableName, false, false, true);
break;
case SQL_UNKNOWN:
transaction.markNotQueryOnly();
default:
break;
@@ -10,6 +10,7 @@ import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.query.DefaultSqlRow;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
@@ -247,7 +248,7 @@ public final class RelationalQueryRequest {
}
if (isLogSql()) {
String logSql = sql;
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
@@ -101,12 +101,12 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* Execute the query as findSet.
*/
Set<?> findSet();
Set<T> findSet();
/**
* Execute the query as findMap.
*/
Map<?, ?> findMap();
<K> Map<K, T> findMap();
/**
* Execute the findSingleAttributeList query.
@@ -61,7 +61,7 @@ public class BootupClassPathSearch {
}
long searchTime = System.currentTimeMillis() - st;
logger.info("Classpath search entities[{}] searchTime[{}] in packages[{}]", bc.getEntities().size(), searchTime, packages);
logger.debug("Classpath search entities[{}] searchTime[{}] in packages[{}]", bc.getEntities().size(), searchTime, packages);
return bc;
} catch (Exception ex) {
@@ -16,6 +16,7 @@ import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeaninternal.server.type.ScalarType;
import org.avaje.classpath.scanner.ClassFilter;
import org.slf4j.Logger;
@@ -470,7 +471,10 @@ public class BootupClasses implements ClassFilter {
return has(cls, Embeddable.class);
}
/**
* Returns true if this class has the annotation (or meta annotation). Does not search recursively.
*/
private boolean has(Class<?> cls, Class<? extends Annotation> ann) {
return cls.getAnnotation(ann) != null;
return AnnotationBase.findAnnotation(cls, ann) != null;
}
}
@@ -1,9 +1,10 @@
package io.ebeaninternal.server.core.bootup;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.ebean.util.StringHelper;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@@ -22,8 +23,6 @@ class ManifestReader {
private final Set<String> packageSet = new HashSet<>();
private static final Pattern PACKAGE_DELIMITER = Pattern.compile("[,; ]");
/**
* Read the packages from ebean.mf manifest files found as resources.
*/
@@ -76,12 +75,8 @@ class ManifestReader {
* Collect each individual package splitting by delimiters.
*/
private void add(String packages) {
String[] split = PACKAGE_DELIMITER.split(packages);
for (String aSplit : split) {
String pkg = aSplit.trim();
if (!pkg.isEmpty()) {
packageSet.add(pkg);
}
for (String pkg : StringHelper.splitNames(packages)) {
packageSet.add(pkg);
}
}
}
@@ -0,0 +1,108 @@
package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.bean.BeanDiffVisitor;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.util.ArrayStack;
import java.io.IOException;
import java.io.StringWriter;
/**
* Builds the 'new values' and 'old values' in JSON form for ChangeLog.
*/
class BeanChangeJson implements BeanDiffVisitor {
private final StringWriter newData;
private final StringWriter oldData;
private final SpiJsonWriter newJson;
private final SpiJsonWriter oldJson;
private final ArrayStack<BeanDescriptor<?>> stack = new ArrayStack<>();
private BeanDescriptor<?> descriptor;
BeanChangeJson(BeanDescriptor<?> descriptor, boolean statelessUpdate) {
this.descriptor = descriptor;
this.newData = new StringWriter(200);
this.newJson = descriptor.createJsonWriter(newData);
newJson.writeStartObject();
if (statelessUpdate) {
this.oldJson = null;
this.oldData = null;
} else {
this.oldData = new StringWriter(200);
this.oldJson = descriptor.createJsonWriter(oldData);
oldJson.writeStartObject();
}
}
@Override
public void visit(int position, Object newVal, Object oldVal) {
try {
BeanProperty prop = descriptor.propertiesIndex[position];
if (prop.isDbUpdatable()) {
prop.jsonWriteValue(newJson, newVal);
if (oldJson != null) {
prop.jsonWriteValue(oldJson, oldVal);
}
}
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
@Override
public void visitPush(int position) {
stack.push(descriptor);
BeanPropertyAssocOne<?> embedded = (BeanPropertyAssocOne<?>)descriptor.propertiesIndex[position];
descriptor = embedded.getTargetDescriptor();
newJson.writeStartObject(embedded.getName());
if (oldJson != null) {
oldJson.writeStartObject(embedded.getName());
}
}
@Override
public void visitPop() {
newJson.writeEndObject();
if (oldJson != null) {
oldJson.writeEndObject();
}
descriptor = stack.pop();
}
/**
* Flush the buffers.
*/
void flush() {
try {
newJson.writeEndObject();
newJson.gen().flush();
if (oldJson != null) {
oldJson.writeEndObject();
oldJson.gen().flush();
}
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
/**
* Return the new values JSON.
*/
String newJson() {
return newData.toString();
}
/**
* Return the old values JSON.
*/
String oldJson() {
return oldData == null ? null : oldData.toString();
}
}
@@ -7,7 +7,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.WriteJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
@@ -62,6 +62,6 @@ public interface BeanCollectionHelp<T> {
/**
* Write the collection out as json.
*/
void jsonWrite(WriteJson ctx, String name, Object collection, boolean explicitInclude) throws IOException;
void jsonWrite(SpiJsonWriter ctx, String name, Object collection, boolean explicitInclude) throws IOException;
}
@@ -9,8 +9,10 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
*/
public class BeanCollectionHelpFactory {
@SuppressWarnings("rawtypes")
static final BeanListHelp LIST_HELP = new BeanListHelp();
@SuppressWarnings("rawtypes")
static final BeanSetHelp SET_HELP = new BeanSetHelp();
/**
@@ -47,7 +47,6 @@ import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedManyIds;
import io.ebeaninternal.server.core.CacheOptions;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.core.DiffHelp;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
@@ -67,7 +66,7 @@ import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebeaninternal.server.query.SplitName;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.WriteJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.util.SortByClause;
import io.ebeaninternal.util.SortByClauseParser;
@@ -83,12 +82,12 @@ import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -340,6 +339,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
*/
private final BeanProperty[] propertiesNonTransient;
protected final BeanProperty[] propertiesIndex;
private final BeanProperty[] propertiesGenInsert;
private final BeanProperty[] propertiesGenUpdate;
/**
* The bean class name or the table name for MapBeans.
@@ -478,6 +479,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.propertiesManySave = listHelper.getManySave();
this.propertiesManyDelete = listHelper.getManyDelete();
this.propertiesManyToMany = listHelper.getManyToMany();
this.propertiesGenInsert = listHelper.getGeneratedInsert();
this.propertiesGenUpdate = listHelper.getGeneratedUpdate();
this.derivedTableJoins = listHelper.getTableJoin();
@@ -797,16 +800,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return ebeanServer.getReadAuditPrepare();
}
public boolean isChangeLog() {
return changeLogFilter != null;
}
/**
* Return true if this request should be included in the change log.
*/
public BeanChange getChangeLogBean(PersistRequestBean<T> request) {
if (changeLogFilter == null) {
return null;
}
PersistRequest.Type type = request.getType();
switch (type) {
switch (request.getType()) {
case INSERT:
return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null;
case UPDATE:
@@ -815,34 +817,79 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
case DELETE:
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) : null;
default:
throw new IllegalStateException("Unhandled request type " + type);
throw new IllegalStateException("Unhandled request type " + request.getType());
}
}
private BeanChange beanChange(ChangeType type, Object id, String data, String oldData) {
Object tenantId = ebeanServer.currentTenantId();
return new BeanChange(name, tenantId, id, type, data, oldData);
}
/**
* Return the bean change for a delete.
*/
private BeanChange deleteBeanChange(PersistRequestBean<T> request) {
return newBeanChange(request.getBeanId(), ChangeType.DELETE, Collections.<String, ValuePair>emptyMap());
return beanChange(ChangeType.DELETE, request.getBeanId(), null, null);
}
/**
* Return the bean change for an update.
* Return the bean change for an update generating 'new values' and 'old values' in JSON form.
*/
private BeanChange updateBeanChange(PersistRequestBean<T> request) {
return newBeanChange(request.getBeanId(), ChangeType.UPDATE, diffFlatten(request.getEntityBeanIntercept().getDirtyValues()));
try {
BeanChangeJson changeJson = new BeanChangeJson(this, request.isStatelessUpdate());
request.getEntityBeanIntercept().addDirtyPropertyValues(changeJson);
changeJson.flush();
return beanChange(ChangeType.UPDATE, request.getBeanId(), changeJson.newJson(), changeJson.oldJson());
} catch (RuntimeException e) {
logger.error("Failed to write ChangeLog entry for update", e);
return null;
}
}
/**
* Return the bean change for an insert.
*/
private BeanChange insertBeanChange(PersistRequestBean<T> request) {
return newBeanChange(request.getBeanId(), ChangeType.INSERT, diffForInsert(request.getEntityBean()));
try {
StringWriter writer = new StringWriter(200);
SpiJsonWriter jsonWriter = createJsonWriter(writer);
jsonWriteForInsert(jsonWriter, request.getEntityBean());
jsonWriter.gen().flush();
return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null);
} catch (IOException e) {
logger.error("Failed to write ChangeLog entry for insert", e);
return null;
}
}
private BeanChange newBeanChange(Object id, ChangeType changeType, Map<String, ValuePair> values) {
Object tenantId = ebeanServer.currentTenantId();
return new BeanChange(getBaseTable(), tenantId, id, changeType, values);
SpiJsonWriter createJsonWriter(StringWriter writer) {
return ebeanServer.jsonExtended().createJsonWriter(writer);
}
/**
* Populate the diff for inserts with flattened non-null property values.
*/
protected void jsonWriteForInsert(SpiJsonWriter jsonWriter, EntityBean newBean) throws IOException {
jsonWriter.writeStartObject();
for (BeanProperty prop : propertiesBaseScalar) {
prop.jsonWriteForInsert(jsonWriter, newBean);
}
for (BeanPropertyAssocOne<?> prop : propertiesOne) {
prop.jsonWriteForInsert(jsonWriter, newBean);
}
for (BeanPropertyAssocOne<?> prop : propertiesEmbedded) {
prop.jsonWriteForInsert(jsonWriter, newBean);
}
jsonWriter.writeEndObject();
}
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
@@ -1046,6 +1093,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return this;
}
/**
* Return the full name taking into account inheritance.
*/
public String rootName() {
if (inheritInfo != null && !inheritInfo.isRoot()) {
return inheritInfo.getRoot().desc().getName();
}
return name;
}
/**
* Return the named ORM query.
*/
@@ -1737,7 +1794,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* account inheritance.
*/
public BeanProperty getBeanPropertyFromPath(String path) {
BeanDescriptor other = this;
BeanDescriptor<?> other = this;
while (true) {
String[] split = SplitName.splitBegin(path);
@@ -1761,7 +1818,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* Return the BeanDescriptor for a given path of Associated One or Many beans.
*/
public BeanDescriptor<?> getBeanDescriptor(String path) {
BeanDescriptor result = this;
BeanDescriptor<?> result = this;
while (true) {
if (path == null) {
return result;
@@ -1796,7 +1853,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* </p>
*/
public BeanPropertyAssocOne<?> getUnidirectional() {
BeanDescriptor other = this;
BeanDescriptor<?> other = this;
while (true) {
if (other.unidirectional != null) {
return other.unidirectional;
@@ -2019,8 +2076,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
public ElComparator<T> getElComparator(String propNameOrSortBy) {
ElComparator<T> c = comparatorCache.computeIfAbsent(propNameOrSortBy, this::createComparator);
return c;
return comparatorCache.computeIfAbsent(propNameOrSortBy, this::createComparator);
}
/**
@@ -2737,11 +2793,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
/**
* Set the embedded owner on any embedded bean properties.
* Set all properties to be loaded (recurse to embedded beans).
*/
public void setEmbeddedOwner(EntityBean bean) {
for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) {
aPropertiesEmbedded.setEmbeddedOwner(bean);
public void setAllLoaded(EntityBean bean) {
bean._ebean_getIntercept().setLoadedPropertyAll();
for (BeanPropertyAssocOne<?> embedded : propertiesEmbedded) {
embedded.setAllLoadedEmbedded(bean);
}
}
@@ -2838,45 +2895,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
/**
* Flatten the diff that comes from the entity bean intercept.
*/
Map<String, ValuePair> diffFlatten(Map<String, ValuePair> diff) {
return DiffHelp.flatten(diff, this);
}
/**
* Return a map of the differences between a and b.
* <p>
* A and B must be of the same type. B can be null, in which case the 'dirty
* diff' of a is returned.
* </p>
* <p>
* This intentionally does not include as OneToMany or ManyToMany properties.
* </p>
*/
public Map<String, ValuePair> diffForInsert(EntityBean newBean) {
Map<String, ValuePair> map = new LinkedHashMap<>();
diffForInsert(null, map, newBean);
return map;
}
/**
* Populate the diff for inserts with flattened non-null property values.
*/
public void diffForInsert(String prefix, Map<String, ValuePair> map, EntityBean newBean) {
for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) {
aPropertiesBaseScalar.diffForInsert(prefix, map, newBean);
}
for (BeanPropertyAssocOne<?> aPropertiesOne : propertiesOne) {
aPropertiesOne.diffForInsert(prefix, map, newBean);
}
for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) {
aPropertiesEmbedded.diffForInsert(prefix, map, newBean);
}
}
/**
* Return the diff comparing the bean values.
*/
@@ -3039,23 +3057,37 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return propertiesLocal;
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
/**
* Return the properties set as generated values on insert.
*/
public BeanProperty[] propertiesGenInsert() {
return propertiesGenInsert;
}
/**
* Return the properties set as generated values on update.
*/
public BeanProperty[] propertiesGenUpdate() {
return propertiesGenUpdate;
}
public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirty(writeJson, bean, dirtyProps);
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
public void jsonWrite(WriteJson writeJson, EntityBean bean) throws IOException {
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
jsonHelp.jsonWrite(writeJson, bean, null);
}
public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException {
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException {
jsonHelp.jsonWrite(writeJson, bean, key);
}
protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException {
protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
jsonHelp.jsonWriteProperties(writeJson, bean);
}
@@ -6,8 +6,7 @@ import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.EJson;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.WriteJson;
import io.ebeaninternal.server.text.json.WriteJson.WriteBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -24,7 +23,7 @@ public class BeanDescriptorJsonHelp<T> {
this.inheritInfo = desc.inheritInfo;
}
public void jsonWrite(WriteJson writeJson, EntityBean bean, String key) throws IOException {
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException {
writeJson.writeStartObject(key);
@@ -43,13 +42,12 @@ public class BeanDescriptorJsonHelp<T> {
writeJson.writeEndObject();
}
protected void jsonWriteProperties(WriteJson writeJson, EntityBean bean) throws IOException {
protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
WriteBean writeBean = writeJson.createWriteBean(desc, bean);
writeBean.write(writeJson);
writeJson.writeBean(desc, bean);
}
public void jsonWriteDirty(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
if (inheritInfo == null) {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
@@ -58,7 +56,7 @@ public class BeanDescriptorJsonHelp<T> {
}
}
protected void jsonWriteDirtyProperties(WriteJson writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
writeJson.writeStartObject(null);
// render the dirty properties
@@ -85,7 +83,7 @@ public class BeanDescriptorJsonHelp<T> {
return null;
}
if (JsonToken.START_OBJECT != token) {
throw new JsonParseException("Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
throw new JsonParseException(parser, "Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
}
}
@@ -98,7 +96,7 @@ public class BeanDescriptorJsonHelp<T> {
if (parser.nextToken() != JsonToken.FIELD_NAME) {
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(msg, parser.getCurrentLocation());
throw new JsonParseException(parser, msg, parser.getCurrentLocation());
}
String propName = parser.getCurrentName();
@@ -111,7 +109,7 @@ public class BeanDescriptorJsonHelp<T> {
return jsonReadProperties(jsonRead, bean, path);
}
String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?";
throw new JsonParseException(msg, parser.getCurrentLocation());
throw new JsonParseException(parser, msg, parser.getCurrentLocation());
}
String discValue = parser.nextTextValue();
@@ -38,6 +38,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
@@ -1451,8 +1452,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* If so it is ok for it not to be enhanced.
*/
private boolean isMappedSuperWithNoProperties(Class<?> beanClass) {
// Attention: do not use AnnotationBase.findAnnotation(cls,...) here.
MappedSuperclass annotation = beanClass.getAnnotation(MappedSuperclass.class);
// do not search recursive here
MappedSuperclass annotation = AnnotationBase.findAnnotation(beanClass, MappedSuperclass.class);
if (annotation == null) {
return false;
}

Some files were not shown because too many files have changed in this diff Show More