Compare commits

...
Author SHA1 Message Date
rob bygrave 624d08f46c [maven-release-plugin] prepare release ebean-11.17.1 2018-06-01 15:43:50 +12:00
rob bygrave 14749daf9b Bump to 11.17.1-SNAPSHOT ... as we added a SPI for io.ebeaninternal.api.SpiLogManager (for capturing the io.ebean.SQL log for testing purposes) 2018-06-01 15:42:24 +12:00
rob bygrave 2817875706 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:15:05 +12:00
rob bygrave 3f8d705439 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:10:00 +12:00
rob bygrave e9faef2692 #1401 - FOR UPDATE queries inside a transaction are not honored when beans are already cached (in the persistence context) 2018-06-01 11:33:41 +12:00
rob bygrave d459807af7 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 09:19:26 +12:00
ooknightandRob Bygrave 10ad9ef096 beautify bindNoLog format (#1399) 2018-05-31 20:23:52 +12:00
rob bygrave 11e3649b85 #1398 - Support EQ / "Equal to" predicate on client side encrypted entity property - @Encrypted(dbEncryption = false) 2018-05-31 16:02:49 +12:00
rob bygrave ddc00fedb6 #1397 - @DbForeignKey(noConstraint = true) is ignored on @ManyToMany ... foreign constraints still added 2018-05-31 10:55:58 +12:00
rob bygrave 0aa9208dcc [maven-release-plugin] prepare for next development iteration 2018-05-30 03:36:38 +12:00
rob bygrave e3b1996dc9 [maven-release-plugin] prepare release ebean-11.16.1 2018-05-30 03:36:24 +12:00
rob bygrave 24ca744e06 Bump version to 11.16.1-SNAPSHOT 2018-05-30 03:35:13 +12:00
rob bygrave 7f9ce5bdd4 #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:38 +12:00
rob bygrave 23e5de0819 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement 2018-05-30 03:32:23 +12:00
rob bygrave c23508819f #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:00 +12:00
rob bygrave af030e3da4 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement
ENH: Add merge() API (using default merge options) to EbeanServer, Ebean and BeanRepository enhancement
2018-05-30 03:31:32 +12:00
rob bygrave d8e67d09a9 #1393 - MergeOptionsBuilder build() return MergeOptions interface type 2018-05-29 15:30:39 +12:00
rob bygrave 66c08e663e #1392 - MergeHandler, change regex Pattern to be static 2018-05-29 15:25:38 +12:00
rob bygrave 9a096e272e #1392 - MergeOptionsBuilder setClientGeneratedIds() ... should not be required if the Id property does not have Identity/Sequence generator assigned 2018-05-29 15:24:32 +12:00
rob bygrave 21e87e12cb Adjust test for #1391 ... such that OrganizationTreeNode has at least one db column as part of insert (for PG) 2018-05-29 13:38:17 +12:00
rob bygrave 7c6d7dd4d0 Merge branch 'FOCONIS-fix-create-reference-bean' 2018-05-29 13:28:32 +12:00
Roland Praml c5329e9db0 reformatting 2018-05-28 17:05:52 +02:00
chrishartlandRoland Praml bccb9f8ddb Fix/create reference with descriptor using inheritance (#13)
* FIXED: Discriminator must be ask for inherited classes

* Fixed: Compile

* Refactoring and Testing
2018-05-28 16:54:53 +02:00
rob bygrave c57af29fca [maven-release-plugin] prepare for next development iteration 2018-05-29 00:44:36 +12:00
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
rob bygrave fb5eeb60b1 [maven-release-plugin] prepare release ebean-11.15.11 2018-05-27 19:19:37 +12:00
rob bygrave 33ec8abb9d No effective change - update test remove unused exception 2018-05-27 17:38:33 +12:00
rob bygrave bbd7baf1be #1388 - AutoTune - tuned query includes "null" as fetched property 2018-05-27 17:33:26 +12:00
rob bygrave 2e36402a76 #1387 - Revert #1374 and expose io.ebean.event.ServletContextListener (as public API) 2018-05-27 15:54:32 +12:00
rob bygrave fa4af4ef98 Revert "#1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused"
This reverts commit 95737cd3c5.
2018-05-27 15:41:51 +12:00
rob bygrave ed6a9e2dbb #1386 - ENH: For DbMigration generation - add support for setting version, name, generatePendingDrop, applyPrefix, ddl header 2018-05-27 15:37:45 +12:00
rob bygrave 42d83705b7 #1384 - Change MigrationXmlWriter to not write <!DOCTYPE xml> (as it is not needed/expected now) 2018-05-24 00:15:25 +12:00
rob bygrave ae58d9223a No effective change - add test for PrimaryKeyJoinColumn - insertWithoutExtra 2018-05-24 00:07:30 +12:00
rob bygrave a9a89938f9 #1368 - Remove unused resource - default-create-table.sql ... which is in ebean-migration 2018-05-24 00:06:18 +12:00
rob bygrave 4b91f5fe0b #1380 - Make javax.xml an optional dependency (as it is not part of JDK from 9 onwards) 2018-05-21 01:06:26 +12:00
rob bygrave 85d3caf079 #1367 - Modularized project cannot locate ebean.properties file on Java 10 2018-05-21 00:29:04 +12:00
rob bygrave f6d8c8b618 [maven-release-plugin] prepare for next development iteration 2018-05-21 00:13:59 +12:00
rob bygrave d0fa3f0cb1 [maven-release-plugin] prepare release ebean-11.15.10 2018-05-21 00:13:43 +12:00
Roland PramlandRob Bygrave 41a31c1aa0 trigger based history will recreate view properly and handle default … (#1370)
* trigger based history will recreate view properly and handle default value also for history view

* HistoryTriggers are generated at the end

* Reference Script

* MYSQL: lock all tables at once
2018-05-20 17:19:10 +12:00
Roland PramlandRob Bygrave ab301f970c FIX: correct caching when collection with different inherited beans is put to the cache (#1379)
* FIX: correct cahcing when collection with different inherited beans is put to the cache

* removed some sysouts
2018-05-20 16:53:56 +12:00
rob bygrave d1b7b082a2 [maven-release-plugin] prepare for next development iteration 2018-05-17 12:50:26 +12:00
rob bygrave 5fb3a1e019 [maven-release-plugin] prepare release ebean-11.15.9 2018-05-17 12:50:12 +12:00
rob bygrave cb0dee998b #1375 - Bug: RawSql parsing fails when using placeholders 2018-05-17 12:48:08 +12:00
Roland PramlandRob Bygrave 53b606289d FIX: use LinkedHashMap in enum types to get deterministic ordering (#1378) 2018-05-17 12:26:37 +12:00
rob bygrave 574d5e215b [maven-release-plugin] prepare for next development iteration 2018-05-14 22:19:37 +12:00
rob bygrave 508f2129f5 [maven-release-plugin] prepare release ebean-11.15.8 2018-05-14 22:19:21 +12:00
rob bygrave cdfcbeec43 #1377 - Bump dependency - jsnakeyaml 1.19 to 1.21 2018-05-14 22:17:27 +12:00
rob bygrave 0e9782e6b3 #1376 - Bump dependency - jackson-core from 2.9.0 to 2.9.5 2018-05-14 22:15:58 +12:00
rob bygrave 927fa5f7d3 [maven-release-plugin] prepare for next development iteration 2018-05-14 22:10:34 +12:00
rob bygrave 53a80c7dd1 [maven-release-plugin] prepare release ebean-11.15.7 2018-05-14 22:10:18 +12:00
rob bygrave 89789f3347 #1372 - Missing @OneToOne mapping yields unusable error messages.
Restore mapping.
2018-05-14 21:33:11 +12:00
rob bygrave 9dc5ca602c #1372 - Missing @OneToOne mapping yields unusable error messages. 2018-05-14 21:32:48 +12:00
rob bygrave 95737cd3c5 #1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused 2018-05-14 21:23:13 +12:00
Roland PramlandRob Bygrave 0a49fc9b62 FIX: NPE when fetching inherited beans from cache (#1373) 2018-05-14 21:12:42 +12:00
rob bygrave f8c80c7fd2 [maven-release-plugin] prepare for next development iteration 2018-05-10 17:22:18 +12:00
rob bygrave fc837b6472 [maven-release-plugin] prepare release ebean-11.15.6 2018-05-10 17:22:04 +12:00
rob bygrave cfaedcdbc5 #1371 - Regression in 11.15.5 with @Inheritance on abstract class with discriminatorType = INTEGER 2018-05-10 17:20:09 +12:00
rob bygrave 111bac7514 [maven-release-plugin] prepare for next development iteration 2018-05-06 20:50:56 +12:00
rob bygrave 1a2ca63d65 [maven-release-plugin] prepare release ebean-11.15.5 2018-05-06 20:50:40 +12:00
rob bygrave 33031d1c69 #1369 - BeanListener and/or BeanPersistAdapter not fired on delete of children when cascaded 2018-05-06 20:21:10 +12:00
Roland PramlandRob Bygrave 49d9d88fbe FIX: load all props from cache when inheritance is involved (#1366) 2018-04-27 14:44:18 +12:00
Rob Bygrave 756f867ed2 #1365 - Regression: property ebean.namingConvention=io.ebean.config.MatchingNamingConvention ... no longer works 2018-04-18 21:24:37 +12:00
Rob Bygrave 12108ada38 #1363 - Update ebean-migration to 11.5.4 2018-04-18 20:47:59 +12:00
rob bygrave fb2fdc9b53 Merge branch 'master' of github.com:ebean-orm/ebean 2018-04-12 20:44:57 +12:00
rob bygrave d570971fd1 #1363 - Update ebean-migration to 11.5.2 2018-04-12 20:44:39 +12:00
André CamiloandRob Bygrave a8434f22ff Inheritance - Add support for @DiscriminatorValue to be optional (#1361) 2018-04-12 20:38:09 +12:00
rob bygrave b0d3921264 [maven-release-plugin] prepare for next development iteration 2018-04-05 22:23:18 +12:00
rob bygrave 4167dfc4af [maven-release-plugin] prepare release ebean-11.15.4 2018-04-05 22:23:03 +12:00
rob bygrave e49a0c9ffb #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints
Additional tests
2018-04-05 21:48:54 +12:00
rob bygrave dd6f223ae1 #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints 2018-04-05 21:35:29 +12:00
Roman ParshikovandRob Bygrave 05aff53241 Fix Maven links in README (#1358) 2018-04-03 21:17:46 +12:00
Rob Bygrave 9a3362e842 [maven-release-plugin] prepare for next development iteration 2018-03-29 14:35:40 +13:00
Rob Bygrave 04e0069eb3 [maven-release-plugin] prepare release ebean-11.15.3 2018-03-29 14:35:25 +13:00
Rob Bygrave b8c4e1c3d3 #1357 - Regression in 11.15.2 - for nested Ebean.beginTransaction(TxScope.requiresNew()); ... where closed via ebeanServer.endTransaction(); rather than transaction.end() 2018-03-29 10:22:12 +13:00
Rob Bygrave 6b136248fa [maven-release-plugin] prepare for next development iteration 2018-03-29 01:49:43 +13:00
215 changed files with 4881 additions and 478 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
## Current versions
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
+11 -6
View File
@@ -9,15 +9,20 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.2</version>
<version>11.17.1</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<properties>
<jackson-core.version>2.9.5</jackson-core.version>
<snakeyaml.version>1.21</snakeyaml.version>
</properties>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.2</tag>
<tag>ebean-11.17.1</tag>
</scm>
<profiles>
@@ -89,7 +94,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
<version>${snakeyaml.version}</version>
</dependency>
<!--
@@ -130,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.5.1</version>
<version>11.5.4</version>
</dependency>
<dependency>
@@ -143,14 +148,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
<scope>provided</scope>
</dependency>
@@ -167,6 +167,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
return db().deletePermanent(bean);
}
/**
* Merge this entity using the default merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object)
*/
public void merge(T bean) {
db().merge(bean);
}
/**
* Merge this entity using the specified merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object, MergeOptions)
*/
public void merge(T bean, MergeOptions options) {
db().merge(bean, options);
}
/**
* Refreshes this entity from the database.
*
+9
View File
@@ -638,6 +638,15 @@ public final class Ebean {
serverMgr.getDefaultServer().updateAll(beans);
}
/**
* Merge the bean using the default merge options.
*
* @param bean The bean to merge
*/
public static void merge(Object bean) {
serverMgr.getDefaultServer().merge(bean);
}
/**
* Merge the bean using the given merge options.
*
+7
View File
@@ -1628,6 +1628,13 @@ public interface EbeanServer {
*/
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Merge the bean using the default merge options (no paths specified, default delete).
*
* @param bean The bean to merge
*/
void merge(Object bean);
/**
* Merge the bean using the given merge options.
*
@@ -8,11 +8,20 @@ import java.util.Set;
*/
public class MergeOptionsBuilder {
private static final MOptions DEFAULT_OPTIONS = new MOptions();
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent = true;
private boolean deletePermanent;
/**
* Return the default options.
*/
public static MergeOptions defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Add a path that will included in the merge.
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
/**
* Build and return the MergeOptions instance.
*/
public MOptions build() {
public MergeOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
@@ -57,6 +66,12 @@ public class MergeOptionsBuilder {
private final boolean deletePermanent;
private final Set<String> paths;
private MOptions(){
this.clientGeneratedIds = false;
this.paths = new LinkedHashSet<>();
this.deletePermanent = false;
}
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
this.paths = paths;
this.clientGeneratedIds = clientGeneratedIds;
+48 -7
View File
@@ -19,15 +19,17 @@ package io.ebean;
* <p>
* <pre>{@code
*
* // example that uses 'named' parameters
* String s = "UPDATE f_topic set post_count = :count where id = :id";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
* // example that uses 'named' parameters
*
* int modifiedCount = Ebean.execute(update);
* String s = "UPDATE f_topic set post_count = :count where id = :id";
*
* String msg = "There were " + modifiedCount + " rows updated";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
*
* int modifiedCount = update.execute();
*
* String msg = "There were " + modifiedCount + " rows updated";
*
* }</pre>
* <p>
@@ -57,6 +59,33 @@ package io.ebean;
*
* txn.commit();
* }
* }</pre>
* <p>
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
* </p>
* <pre>{@code
*
* try (Transaction txn = Ebean.beginTransaction()) {
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
*
* txn.commit();
* }
*
* }</pre>
*
@@ -83,6 +112,18 @@ public interface SqlUpdate {
*/
int execute();
/**
* Execute when addBatch() has been used to batch multiple bind executions.
*
* @return The row counts for each of the batched statements.
*/
int[] executeBatch();
/**
* Add the statement to batch processing to then later execute via executeBatch().
*/
void addBatch();
/**
* Return the generated key value.
*/
+15 -3
View File
@@ -23,6 +23,7 @@ public interface Transaction extends AutoCloseable {
* This returns the current transaction for the 'default server'. If you are using
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
* </p>
*
* @see Ebean#currentTransaction()
* @see EbeanServer#currentTransaction()
*/
@@ -136,6 +137,18 @@ public interface Transaction extends AutoCloseable {
*/
void rollback(Throwable e) throws PersistenceException;
/**
* Set when we want nested transactions to use Savepoint's.
* <p>
* This means that for a nested transaction:
* <ul>
* <li>begin transaction maps to creating a savepoint</li>
* <li>commit transaction maps to releasing a savepoint</li>
* <li>rollback transaction maps to rollback a savepoint</li>
* </ul>
*/
void setNestedUseSavepoint();
/**
* Mark the transaction for rollback only.
*/
@@ -151,7 +164,6 @@ public interface Transaction extends AutoCloseable {
*/
void end();
/**
* Synonym for end() to support AutoClosable.
*/
@@ -227,7 +239,7 @@ public interface Transaction extends AutoCloseable {
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
* </p>
* <p>
*
* <pre>{@code
*
* // assume Customer has L2 bean caching enabled ...
@@ -309,7 +321,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* Example: batch processing executing every 3 rows
* </p>
* <p>
*
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
@@ -402,9 +402,20 @@ public class DbMigrationConfig {
* You can use placeholders like ${version} or ${timestamp} in properties file.
*/
public String getDdlHeader() {
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
return ddlHeader;
}
/**
* Set the header prepended to the DDL.
*/
public void setDdlHeader(String ddlHeader) {
this.ddlHeader = ddlHeader;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
@@ -475,10 +486,6 @@ public class DbMigrationConfig {
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
dbPassword = properties.get("migration.dbpassword", adminPwd);
ddlHeader = properties.get("ddl.header", ddlHeader);
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
}
/**
@@ -493,6 +493,11 @@ public class ServerConfig {
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1967,7 +1972,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2772,6 +2777,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -2871,7 +2877,7 @@ public class ServerConfig {
}
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingconvention", null);
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingConvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -3109,6 +3115,23 @@ public class ServerConfig {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -37,7 +37,7 @@ class LoadContext {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
is = getClass().getResourceAsStream("/" + resourcePath);
is = resourceStream(resourcePath);
if (is != null) {
loadedResources.add(resourcePath);
}
@@ -56,6 +56,15 @@ class LoadContext {
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
/**
* Add a property entry.
*/
@@ -93,6 +93,38 @@ public interface DbMigration {
*/
void setStrictMode(boolean strictMode);
/**
* Set to true to include a generated header comment in the DDL script.
*/
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
/**
* Set the header that is included in the generated DDL script.
*/
void setHeader(String header);
/**
* Set the prefix for the version. Set this to "V" for use with Flyway.
*/
void setApplyPrefix(String applyPrefix);
/**
* Set the version of the migration to be generated.
*/
void setVersion(String version);
/**
* Set the name of the migration to be generated.
*/
void setName(String name);
/**
* Generate a migration for the version specified that contains pending drops.
*
* @param generatePendingDrop The version of a prior migration that holds pending drops.
*/
void setGeneratePendingDrop(String generatePendingDrop);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -0,0 +1,38 @@
package io.ebean.event;
import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Listens for webserver server starting and stopping events.
* <p>
* This should be used when the deployment is into a servlet container where the webapp
* can be shutdown or redeployed without the JVM stopping.
* </p>
* <p>
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
* runnable war or when using a servlet container that only contains the single webapp and
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
* hook that Ebean registers.
* </p>
*/
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
* The servlet container is stopping.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
ShutdownManager.shutdown();
}
/**
* Do nothing on startup.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
}
}
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
}
Props getProps(String path) {
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
return props;
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
}
public Collection<Props> getPathProps() {
@@ -31,6 +31,11 @@ import java.util.function.Predicate;
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the log manager.
*/
SpiLogManager log();
/**
* Return the server extended Json context.
*/
@@ -268,4 +273,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return true if a row for the bean type and id exists.
*/
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
/**
* Add to JDBC batch for later execution.
*/
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the batched statement.
*/
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
/**
* Log manager for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogManager {
/**
* Return the SQL logger.
*/
SpiLogger sql();
/**
* Return the TXN logger.
*/
SpiLogger txn();
/**
* Return the Summary logger.
*/
SpiLogger sum();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.api;
/**
* Logger for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogger {
/**
* Is debug logging enabled.
*/
boolean isDebug();
/**
* Is trace logging enabled.
*/
boolean isTrace();
/**
* Log a debug level message.
*/
void debug(String msg);
/**
* Log a trace level message.
*/
void trace(String msg);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
*/
public interface SpiLoggerFactory {
/**
* Create the logger given the name.
*/
SpiLogger create(String name);
}
@@ -644,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
boolean isBeanCachePut();
/**
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
* Return true if we must hit the DB (Cache reload or select for update).
*/
boolean isBeanCacheReload();
boolean isForceHitDatabase();
/**
* Return the cache mode for using the bean cache (Get and Put).
@@ -319,4 +319,9 @@ public interface SpiTransaction extends Transaction {
* Return the profile location for this transaction.
*/
ProfileLocation getProfileLocation();
/**
* Return true when nested transactions should create Savepoints.
*/
boolean isNestedUseSavepoint();
}
@@ -21,7 +21,7 @@ import java.sql.SQLException;
/**
* Proxy for an underlying SpiTransaction (most of the API).
*/
abstract class SpiTransactionProxy implements SpiTransaction {
public abstract class SpiTransactionProxy implements SpiTransaction {
protected SpiTransaction transaction;
@@ -50,6 +50,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.isRollbackOnly();
}
@Override
public void setNestedUseSavepoint() {
transaction.setNestedUseSavepoint();
}
@Override
public boolean isNestedUseSavepoint() {
return transaction.isNestedUseSavepoint();
}
@Override
public long profileOffset() {
return transaction.profileOffset();
@@ -91,6 +91,12 @@ public class DefaultDbMigration implements DbMigration {
protected DbConstraintNaming constraintNaming;
protected Boolean strictMode;
protected Boolean includeGeneratedFileComment;
protected String header;
protected String applyPrefix;
protected String version;
protected String name;
protected String generatePendingDrop;
/**
* Create for offline migration generation.
@@ -148,6 +154,36 @@ public class DefaultDbMigration implements DbMigration {
this.strictMode = strictMode;
}
@Override
public void setApplyPrefix(String applyPrefix) {
this.applyPrefix = applyPrefix;
}
@Override
public void setVersion(String version) {
this.version = version;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
@Override
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
@Override
public void setHeader(String header) {
this.header = header;
}
/**
* Set the specific platform to generate DDL for.
* <p>
@@ -512,8 +548,28 @@ public class DefaultDbMigration implements DbMigration {
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
if (strictMode != null && migrationConfig != null) {
migrationConfig.setStrictMode(strictMode);
if (migrationConfig != null) {
if (strictMode != null) {
migrationConfig.setStrictMode(strictMode);
}
if (applyPrefix != null) {
migrationConfig.setApplyPrefix(applyPrefix);
}
if (header != null) {
migrationConfig.setDdlHeader(header);
}
if (includeGeneratedFileComment != null) {
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
}
if (version != null) {
migrationConfig.setVersion(version);
}
if (name != null) {
migrationConfig.setName(name);
}
if (generatePendingDrop != null) {
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
}
}
}
@@ -18,7 +18,9 @@ public class DdlWrite {
private final DdlBuffer applyForeignKeys;
private final DdlBuffer applyHistory;
private final DdlBuffer applyHistoryView;
private final DdlBuffer applyHistoryTrigger;
private final DdlBuffer dropAllForeignKeys;
@@ -39,7 +41,8 @@ public class DdlWrite {
this.applyDropDependencies = new BaseDdlBuffer(configuration);
this.apply = new BaseDdlBuffer(configuration);
this.applyForeignKeys = new BaseDdlBuffer(configuration);
this.applyHistory = new BaseDdlBuffer(configuration);
this.applyHistoryView = new BaseDdlBuffer(configuration);
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
this.dropAll = new BaseDdlBuffer(configuration);
}
@@ -61,7 +64,8 @@ public class DdlWrite {
public boolean isApplyEmpty() {
return apply.getBuffer().isEmpty()
&& applyForeignKeys.getBuffer().isEmpty()
&& applyHistory.getBuffer().isEmpty()
&& applyHistoryView.getBuffer().isEmpty()
&& applyHistoryTrigger.getBuffer().isEmpty()
&& applyDropDependencies.getBuffer().isEmpty();
}
@@ -90,10 +94,17 @@ public class DdlWrite {
}
/**
* Return the buffer that apply history DDL is written to.
* Return the buffer that apply history-view DDL is written to.
*/
public DdlBuffer applyHistory() {
return applyHistory;
public DdlBuffer applyHistoryView() {
return applyHistoryView;
}
/**
* Return the buffer that apply history-trigger DDL is written to.
*/
public DdlBuffer applyHistoryTrigger() {
return applyHistoryTrigger;
}
/**
@@ -42,6 +42,12 @@ import java.util.Map;
*/
public class BaseTableDdl implements TableDdl {
enum HistorySupport {
NONE,
SQL2011,
TRIGGER_BASED
}
protected final DbConstraintNaming naming;
protected final NamingConvention namingConvention;
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
private boolean strictMode;
private final boolean sql2011History;
private final HistorySupport historySupport;
/**
* 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;
private final List<String> before;
private final List<String> after;
private final String tableName;
private final String columnName;
private final String defaultValue;
private final boolean withHistory;
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) {
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
this.withHistory = withHistory;
}
/**
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
withHistory = isTrue(alter.isWithHistory());
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError(tableName, columnName);
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
if (!before.isEmpty()) {
buffer.end();
}
if (!before.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
}
void writeAfter(DdlBuffer buffer) throws IOException {
if (!after.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
// here we run post migration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
this.platformDdl.configure(serverConfig);
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
this.sql2011History = hist != null && hist.isStandardsBased();
if (hist == null) {
this.historySupport = HistorySupport.NONE;
} else {
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
}
}
/**
@@ -700,8 +720,14 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generateEpilog(DdlWrite write) throws IOException {
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
if (!regenerateHistoryTriggers.isEmpty()) {
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
}
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
}
platformDdl.generateEpilog(write);
}
@@ -722,15 +748,15 @@ public class BaseTableDdl implements TableDdl {
String tableName = addColumn.getTableName();
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), tableName, column, false);
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
}
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// make same changes to the history table
String historyTable = historyTable(tableName);
for (Column column : columns) {
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
alterTableAddColumn(writer.apply(), historyTable, column, true);
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
}
@@ -771,7 +797,8 @@ public class BaseTableDdl implements TableDdl {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// also drop from the history table
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
@@ -872,8 +899,9 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
// mysql and sql server column type change allowing nulls in the history table column
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
AlterColumn alterHistoryColumn = new AlterColumn();
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
alterHistoryColumn.setColumnName(alter.getColumnName());
@@ -923,7 +951,8 @@ 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()) && !sql2011History) {
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
// 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();
@@ -979,8 +1008,8 @@ public class BaseTableDdl implements TableDdl {
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
}
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
if (!onHistoryTable) {
help.writeBefore(buffer);
}
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
*/
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
}
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
createHistoryTable(apply, table);
@@ -210,19 +210,19 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
.endOfStatement().end();
}
/**
* Create or replace the with_history view with explicit columns.
*/
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
/**
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
}
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyBuffer() {
return writer.applyHistory();
public DdlBuffer historyViewBuffer() {
return writer.applyHistoryView();
}
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyTriggerBuffer() {
return writer.applyHistoryTrigger();
}
/**
* Return the appropriate drop dependency buffer for the current mode.
*/
@@ -27,7 +27,7 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
}
@@ -35,7 +35,9 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
dropTriggers(buffer, update.getBaseTable());
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
}
@@ -17,7 +17,8 @@ public class HistoryTableUpdate {
ADD,
DROP,
INCLUDE,
EXCLUDE
EXCLUDE,
ALTER
}
private static class Column {
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import java.io.IOException;
import java.util.Collection;
/**
* MySql specific DDL.
@@ -113,10 +114,39 @@ public class MySqlDdl extends PlatformDdl {
}
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// alter comment currently not supported as it requires to repeat whole column definition
}
/**
* Locks all tables for triggers that have to be updated.
*/
@Override
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
if (!tables.isEmpty()) {
buffer.append("lock tables ");
int i = 0;
for (String table : tables) {
if (i > 0) {
buffer.append(", ");
}
buffer.append(table).append(" write");
i++;
}
buffer.endOfStatement();
}
}
/**
* Unlocks all tables for triggers that have to be updated.
*/
@Override
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
buffer.append("unlock tables").endOfStatement();
}
}
@@ -34,19 +34,19 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
String baseTable = update.getBaseTable();
buffer.append("lock tables ").append(baseTable).append(" write").endOfStatement();
dropTriggers(buffer, baseTable);
addBeforeUpdate(updateTriggerName(baseTable), update);
addBeforeDelete(deleteTriggerName(baseTable), update);
buffer.append("unlock tables").endOfStatement();
}
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
@@ -59,7 +59,7 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
@@ -20,10 +20,12 @@ import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.IdentityType;
import io.ebeaninternal.dbmigration.model.MTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
@@ -442,13 +444,16 @@ public class PlatformDdl {
.append(" ").append(addColumn).append(" ").append(column.getName())
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (defaultValue != null) {
// Add default value also to history table if it is not excluded
if (defaultValue != null) {
if (!onHistoryTable || !isTrue(column.isHistoryExclude())) {
buffer.append(" default ");
buffer.append(defaultValue);
}
}
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
@@ -456,7 +461,8 @@ public class PlatformDdl {
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
column.getCheckConstraint());
buffer.append(ddl).endOfStatement();
}
} else {
@@ -641,4 +647,18 @@ public class PlatformDdl {
}
return name;
}
/**
* Mysql-specific: Locks all tables for triggers that have to be updated.
*/
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
/**
* Mysql-specific: Unlocks all tables for triggers that have to be updated.
*/
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
}
@@ -62,7 +62,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(baseTableName);
String triggerName = triggerName(baseTableName);
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
apply
.append("create trigger ").append(triggerName).newLine()
.append(" before update or delete on ").append(baseTableName).newLine()
@@ -107,7 +107,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String historyTable = historyTableName(table.getName());
List<String> columnNames = columnNamesForApply(table);
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
createOrReplaceFunction(writer.applyHistoryTrigger(), procedureName, historyTable, columnNames);
}
@Override
@@ -116,21 +116,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(update.getBaseTable());
recreateHistoryView(update);
createOrReplaceFunction(update.historyBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
/**
* For postgres we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
private void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view with specific columns specified (the columns generally are not dropped until later)
createWithHistoryView(update);
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
@Override
@@ -31,19 +31,24 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
enableSystemVersioning(writer, baseTable);
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistory();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
private String getHistoryTable(String baseTable) {
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
// Setting SYSTEM_VERSIONING to ON failed because history table 'xxx_history' is not specified in two-part name format.
if (historyTable.indexOf('.') == -1) {
historyTable = "dbo." +historyTable; // so add the default schema, if none was specified.
}
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=").append(historyTable).append("))").endOfStatement();
return historyTable;
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistoryView();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTable)).append("))").endOfStatement();
DdlBuffer drop = writer.dropAll();
drop.append("IF OBJECT_ID('").append(baseTable).append("', 'U') IS NOT NULL alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
@@ -53,7 +58,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
// drop default constraints
@@ -76,7 +81,15 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) throws IOException {
// SQL Server 2016 does not need triggers
DdlBuffer apply = writer.applyHistoryView();
String baseTableName = baseTable.getBaseTable();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = off (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
apply.append("-- history migration goes here").newLine();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
}
}
@@ -29,7 +29,6 @@ public class MigrationXmlWriter {
try (FileWriter writer = new FileWriter(file)) {
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
writer.write("<!DOCTYPE xml>\n");
if (comment != null) {
writer.write("<!-- ");
writer.write(comment);
@@ -113,7 +113,8 @@ public class CurrentModel {
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
ddl.append(write.applyHistory().getBuffer());
ddl.append(write.applyHistoryView().getBuffer());
ddl.append(write.applyHistoryTrigger().getBuffer());
return ddl.toString();
}
@@ -527,5 +527,8 @@ public class MColumn {
if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
}
if (hasValue(alterColumn.isHistoryExclude())) {
historyExclude = alterColumn.isHistoryExclude();
}
}
}
@@ -621,7 +621,7 @@ public class MTable {
DropColumn dropColumn = new DropColumn();
dropColumn.setTableName(name);
dropColumn.setColumnName(existingColumn.getName());
if (withHistory && !existingColumn.isHistoryExclude()) {
if (withHistory) {
// These dropColumns should occur on the history
// table as well as the base table
dropColumn.setWithHistory(Boolean.TRUE);
@@ -97,7 +97,8 @@ public class PlatformDdlWriter {
writer.append("-- apply changes\n");
writer.append(write.apply().getBuffer());
writer.append(write.applyForeignKeys().getBuffer());
writer.append(write.applyHistory().getBuffer());
writer.append(write.applyHistoryView().getBuffer());
writer.append(write.applyHistoryTrigger().getBuffer());
}
private void prependDropDependencies(Writer writer, DdlBuffer buffer) throws IOException {
@@ -5,6 +5,7 @@ import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
@@ -48,10 +49,12 @@ class ModelBuildIntersectionTable {
private void buildFkConstraints() {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
PropertyForeignKey foreignKey = manyProp.getForeignKey();
if (foreignKey == null || !foreignKey.isNoConstraint()) {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
}
intersectionTable.checkDuplicateForeignKeys();
}
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
@@ -15,6 +15,10 @@ public class CachedBeanDataToBean {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
BeanProperty idProperty = desc.getIdProperty();
if (desc.getInheritInfo() != null) {
desc = desc.getInheritInfo().readType(bean.getClass()).desc();
}
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
@@ -170,11 +170,7 @@ public abstract class AbstractSqlQueryRequest {
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
@@ -13,6 +13,7 @@ import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.MergeOptions;
import io.ebean.MergeOptionsBuilder;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
@@ -63,8 +64,10 @@ import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEventTable;
@@ -189,6 +192,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
@@ -229,6 +234,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
@@ -315,6 +321,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public SpiLogManager log() {
return logManager;
}
@Override
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
@@ -843,7 +854,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void endTransaction() {
Transaction transaction = currentTransaction();
Transaction transaction = transactionManager.getInScope();
if (transaction != null) {
transaction.end();
}
@@ -919,6 +930,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultUpdateQuery<>(createQuery(beanType));
}
@Override
public void merge(Object bean) {
merge(bean, MergeOptionsBuilder.defaultOptions(), null);
}
@Override
public void merge(Object bean, MergeOptions options) {
merge(bean, options, null);
@@ -1159,7 +1175,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
@@ -1961,6 +1977,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return persister.executeSqlUpdate(updSql, t);
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
persister.addBatch(sqlUpdate, transaction);
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
return persister.executeBatch(sqlUpdate, transaction);
}
/**
* Execute the updateSql.
*/
@@ -1,11 +1,12 @@
package io.ebeaninternal.server.core;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebean.Update;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import java.io.Serializable;
@@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private static final long serialVersionUID = -6493829438421253102L;
private transient final EbeanServer server;
private transient final SpiEbeanServer server;
/**
* The parameters used to bind to the sql.
@@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private Object generatedKey;
/**
* Set when batching explicitly used.
*/
private boolean batched;
/**
* Transaction used for addBatch() executeBatch() processing.
*/
private transient SpiTransaction transaction;
/**
* Create with server sql and bindParams object.
* <p>
@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* same time.
* </p>
*/
public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) {
this.server = server;
this.sql = sql;
this.bindParams = bindParams;
@@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* Create with a specific server. This means you can use the
* SqlUpdate.execute() method.
*/
public DefaultSqlUpdate(EbeanServer server, String sql) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql) {
this(server, sql, new BindParams());
}
@@ -111,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
@Override
public int execute() {
if (server != null) {
if (batched) {
server.executeBatch(this, transaction);
return -1;
}
return server.execute(this);
} else {
// Hopefully this doesn't catch anyone out...
@@ -118,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public int[] executeBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (!batched) {
throw new IllegalStateException("No prior addBatch() called?");
}
return server.executeBatch(this, transaction);
}
@Override
public void addBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (transaction == null) {
transaction = server.currentServerTransaction();
if (transaction == null) {
throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()");
}
}
batched = true;
server.addBatch(this, transaction);
}
@Override
public Object getGeneratedKey() {
return generatedKey;
@@ -34,8 +34,9 @@ class InternalConfigXmlRead {
InternalConfigXmlRead(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
this.classLoader = serverConfig.getClassLoadConfig().getClassLoader();
init();
if (serverConfig.getClassLoadConfig().isJavaxJAXBPresent()) {
init();
}
}
private void init() {
@@ -73,10 +74,12 @@ class InternalConfigXmlRead {
* Return the named queries for Dto beans.
*/
Map<Class<?>, DtoNamedQueries> readDtoMapping() {
for (XmEbean mapping : xmlEbeanList) {
List<XmDto> dtoList = mapping.getDto();
for (XmDto dto : dtoList) {
readDtoMapping(dto);
if (xmlEbeanList != null) {
for (XmEbean mapping : xmlEbeanList) {
List<XmDto> dtoList = mapping.getDto();
for (XmDto dto : dtoList) {
readDtoMapping(dto);
}
}
}
@@ -20,6 +20,9 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
@@ -44,6 +47,8 @@ import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import io.ebeaninternal.server.logger.DLogManager;
import io.ebeaninternal.server.logger.DLoggerFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
@@ -134,16 +139,19 @@ public class InternalConfiguration {
private final MultiValueBind multiValueBind;
private final SpiLogManager logManager;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.serverConfig = serverConfig;
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
this.jsonFactory = serverConfig.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.cacheManager = cacheManager;
this.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
this.databasePlatform = serverConfig.getDatabasePlatform();
@@ -169,6 +177,20 @@ public class InternalConfiguration {
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = serverConfig.service(SpiLoggerFactory.class);
if (loggerFactory == null) {
loggerFactory = new DLoggerFactory();
}
SpiLogger sql = loggerFactory.create("io.ebean.SQL");
SpiLogger sum = loggerFactory.create("io.ebean.SUM");
SpiLogger txn = loggerFactory.create("io.ebean.TXN");
return new DLogManager(sql, sum, txn);
}
/**
* Create and return the ExpressionFactory based on configuration and database platform.
*/
@@ -262,9 +284,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -378,7 +400,7 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -511,4 +533,8 @@ public class InternalConfiguration {
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
public SpiLogManager getLogManager() {
return logManager;
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.core;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
@@ -27,15 +26,17 @@ public final class PersistRequestUpdateSql extends PersistRequest {
private String description;
private boolean addBatch;
/**
* Create.
*/
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate,
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) sqlUpdate;
this.updateSql = sqlUpdate;
updateSql.reset();
}
@@ -44,11 +45,24 @@ public final class PersistRequestUpdateSql extends PersistRequest {
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
}
/**
* Add this statement to JDBC batch for later execution.
*/
public int addBatch() {
this.addBatch = true;
return executeOrQueue();
}
@Override
public int executeNow() {
return persistExecute.executeSqlUpdate(this);
}
@Override
public boolean isBatchThisRequest() {
return addBatch || super.isBatchThisRequest();
}
@Override
public int executeOrQueue() {
return executeStatement();
@@ -8,6 +8,7 @@ import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -91,4 +92,14 @@ public interface Persister {
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
/**
* Add the statement to JDBC batch for later execution via executeBatch.
*/
void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
/**
* Execute the associated batched statement.
*/
int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
}
@@ -5,12 +5,15 @@ import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Deprecated - migrate to io.ebean.event.ServletContextListener.
* <p>
* Listens for webserver server starting and stopping events.
* <p>
* Register this listener in the web.xml configuration file. This will listen
* for startup and shutdown events.
* </p>
*/
@Deprecated
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
@@ -71,6 +71,7 @@ import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.query.STreePropertyAssocOne;
import io.ebeaninternal.server.query.STreeType;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.type.DataBind;
@@ -151,6 +152,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
private final IdType idType;
/**
* Set when Id property is marked with GeneratedValue annotation.
*/
private final boolean idGeneratedValue;
private final boolean idTypePlatformDefault;
private final PlatformIdGenerator idGenerator;
@@ -449,6 +455,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.defaultSelectClause = deploy.getDefaultSelectClause();
this.idType = deploy.getIdType();
this.idGeneratedValue = deploy.isIdGeneratedValue();
this.idTypePlatformDefault = deploy.isIdTypePlatformDefault();
this.idGenerator = deploy.getIdGenerator();
this.sequenceName = deploy.getSequenceName();
@@ -630,9 +637,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
public void setEbeanServer(SpiEbeanServer ebeanServer) {
this.ebeanServer = ebeanServer;
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
for (BeanPropertyAssocMany<?> assocMany : propertiesMany) {
// used for creating lazy loading lists etc
aPropertiesMany.setLoader(ebeanServer);
assocMany.setEbeanServer(ebeanServer);
}
}
@@ -1723,7 +1730,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* associated L2 bean caching.
*/
public boolean isDeleteByStatement() {
return deleteRecurseSkippable && !isBeanCaching();
return persistListener == null
&& persistController == null
&& deleteRecurseSkippable && !isBeanCaching();
}
public boolean isDeleteByBulk() {
return persistListener == null
&& persistController == null
&& propertiesManyToMany.length == 0;
}
/**
@@ -1870,6 +1885,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* on first access (lazy load) or immediately (eager load)
*/
public EntityBean createEntityBean(boolean isNew) {
if (prototypeEntityBean == null) {
throw new UnsupportedOperationException("cannot create entity bean for abstract entity " + getName());
}
try {
EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance();
@@ -1910,6 +1928,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return createEntityBean();
}
/**
* 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)
*/
private T findReferenceBean(Object id, PersistenceContext pc) {
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
query.setPersistenceContext(pc);
return query
// .select(getIdProperty().getName())
// we do not select the id because we
// probably have to load the entire bean
.setId(id).findOne();
}
/**
* Create a reference bean based on the id.
*/
@@ -1929,6 +1961,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
@@ -1961,6 +1997,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public T createReference(Object id, PersistenceContext pc) {
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
@@ -2965,6 +3005,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return idType;
}
/**
* Return true if the Id value is marked as a <code>@GeneratedValue</code>.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Return true if the identity is the platform default (not explicitly set).
*/
@@ -447,7 +447,21 @@ final class BeanDescriptorCacheHelp<T> {
void beanPutAll(Collection<EntityBean> beans) {
if (desc.inheritInfo != null) {
Class<?> aClass = theClassOf(beans);
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
// check if all beans have the same class
for (EntityBean bean : beans) {
if (!bean.getClass().equals(aClass)) {
aClass = null;
break;
}
}
if (aClass == null) {
// there are different bean types in the collection, so we add one by one to the cache
for (EntityBean bean : beans) {
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
}
} else {
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
}
} else {
beanCachePutAllDirect(beans);
}
@@ -394,12 +394,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
private void readXmlMapping(List<XmEbean> mappings) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
if (mappings != null) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
}
}
}
}
@@ -1368,9 +1369,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setIdType(IdType.EXTERNAL);
return;
}
// use the default. IDENTITY or SEQUENCE.
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
if (desc.isIdGeneratedValue() || serverConfig.isIdGeneratorAutomatic()) {
// use IDENTITY or SEQUENCE based on platform
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
} else {
// externally/application supplied Id values
desc.setIdType(IdType.EXTERNAL);
return;
}
}
if (desc.getBaseTable() == null) {
@@ -1386,21 +1393,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return;
}
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (IdType.SEQUENCE == desc.getIdType()) {
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
@@ -78,6 +78,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
return false;
}
@Override
public Object localEncrypt(Object value) {
throw new IllegalArgumentException("Should not get here?");
}
@Override
public boolean containsFormulaWithJoin() {
return false;
@@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.LocalEncryptedType;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
import io.ebeaninternal.server.type.ScalarTypeEnum;
@@ -1246,6 +1247,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return dbBind;
}
@Override
public Object localEncrypt(Object value) {
return ((LocalEncryptedType)scalarType).localEncrypt(value);
}
/**
* Returns true if DB encrypted.
*/
@@ -65,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
final TableJoin tableJoin;
final PropertyForeignKey foreignKey;
/**
* The type of the joined bean.
*/
@@ -90,6 +92,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
public BeanPropertyAssoc(BeanDescriptor<?> descriptor, DeployBeanPropertyAssoc<T> deploy) {
super(descriptor, deploy);
this.foreignKey = deploy.getForeignKey();
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
@@ -128,6 +131,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
return fetchPreference;
}
/**
* Return the extra configuration for the foreign key.
*/
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Create a ElPropertyValue for a *ToOne or *ToMany.
*/
@@ -7,10 +7,10 @@ import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -50,6 +50,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
private final String intersectionPublishTable;
private final String intersectionDraftTable;
private IntersectionTable intersectionTable;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
@@ -157,7 +159,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
if (elementCollection) {
targetDescriptor = elementDescriptor;
} else {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
super.initialiseTargetDescriptor(initContext);
}
}
@@ -352,10 +354,30 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
*/
public void setLoader(BeanCollectionLoader loader) {
public void setEbeanServer(SpiEbeanServer server) {
if (help != null) {
help.setLoader(loader);
help.setLoader(server);
}
if (manyToMany) {
intersectionTable = initIntersectionTable();
}
}
private IntersectionTable initIntersectionTable() {
IntersectionBuilder row = new IntersectionBuilder(intersectionPublishTable, intersectionDraftTable);
for (ExportedProperty exportedProperty : exportedProperties) {
row.addColumn(exportedProperty.getForeignDbColumn());
}
importedId.buildImport(row);
return row.build();
}
/**
* Return the intersection table helper.
*/
public IntersectionTable intersectionTable() {
return intersectionTable;
}
/**
@@ -755,6 +777,20 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable);
}
/**
* Bind the two side of a many to many to the given SqlUpdate.
*/
public void intersectionBind(SqlUpdate sql, EntityBean parentBean, EntityBean other) {
if (embeddedExportedProperties) {
BeanProperty idProp = descriptor.getIdProperty();
parentBean = (EntityBean) idProp.getValue(parentBean);
}
for (ExportedProperty exportedProperty : exportedProperties) {
sql.setNextParameter(exportedProperty.getValue(parentBean));
}
importedId.bindImport(sql, other);
}
private void buildExport(IntersectionRow row, EntityBean parentBean) {
if (embeddedExportedProperties) {
@@ -46,15 +46,16 @@ class BeanPropertyAssocManyJsonHelp {
if (JsonToken.VALUE_NULL == event) {
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array ");
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
}
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
@@ -44,8 +44,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private final boolean primaryKeyExport;
private final PropertyForeignKey foreignKey;
private AssocOneHelp localHelp;
protected final BeanProperty[] embeddedProps;
@@ -73,8 +71,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
DeployBeanPropertyAssocOne<T> deploy) {
super(descriptor, deploy);
foreignKey = deploy.getForeignKey();
primaryKeyExport = deploy.isPrimaryKeyExport();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
@@ -310,6 +306,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
String discProperty = prefix + "." + discriminatorColumn;
selectChain.add(discProperty);
}
if (targetIdBinder == null) {
throw new IllegalStateException("No Id binding property for " + getFullBeanName()
+ ". Probably a missing @OneToOne mapping annotation on this relationship?");
}
targetIdBinder.buildRawSqlSelectChain(prefix, selectChain);
} else {
@@ -319,10 +319,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public boolean hasForeignKey() {
return foreignKey == null || !foreignKey.isNoConstraint();
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.parse.DeployInheritInfo;
import io.ebeaninternal.server.query.SqlTreeProperties;
import java.lang.reflect.Modifier;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -291,7 +292,7 @@ public class InheritInfo {
* Return true if this is considered a concrete type in the inheritance hierarchy.
*/
public boolean isConcrete() {
return discriminatorValue != null;
return !Modifier.isAbstract(type.getModifiers());
}
/**
@@ -0,0 +1,85 @@
package io.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to build IntersectionTable.
*/
public class IntersectionBuilder {
private final String publishTable;
private final String draftTable;
private final List<String> columns = new ArrayList<>();
IntersectionBuilder(String publishTable, String draftTable) {
this.publishTable = publishTable;
this.draftTable = draftTable;
}
public void addColumn(String column) {
columns.add(column);
}
public IntersectionTable build() {
String insertSql = insertSql(publishTable);
String deleteSql = deleteSql(publishTable);
String draftInsertSql;
String draftDeleteSql;
if (publishTable.equals(draftTable)) {
draftInsertSql = insertSql;
draftDeleteSql = deleteSql;
} else {
draftInsertSql = insertSql(draftTable);
draftDeleteSql = deleteSql(draftTable);
}
return new IntersectionTable(insertSql, deleteSql, draftInsertSql, draftDeleteSql);
}
private String insertSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("insert into ").append(tableName).append(" (");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(", ");
}
sb.append(column);
}
sb.append(") values (");
for (int i = 0; i < count; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
private String deleteSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(tableName);
sb.append(" where ");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(" and ");
}
sb.append(column);
sb.append(" = ?");
}
return sb.toString();
}
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.IdInExpression;
@@ -22,12 +22,12 @@ public class IntersectionRow {
private List<Object> excludeIds;
private BeanDescriptor<?> excludeDescriptor;
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
this.tableName = tableName;
this.targetDescriptor = targetDescriptor;
}
public IntersectionRow(String tableName) {
IntersectionRow(String tableName) {
this.tableName = tableName;
this.targetDescriptor = null;
}
@@ -35,7 +35,7 @@ public class IntersectionRow {
/**
* Set Id's to exclude. This is for deleting non-attached detail Id's.
*/
public void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
this.excludeIds = excludeIds;
this.excludeDescriptor = excludeDescriptor;
}
@@ -44,7 +44,7 @@ public class IntersectionRow {
values.put(key, value);
}
public SqlUpdate createInsert(EbeanServer server) {
public SqlUpdate createInsert(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -72,7 +72,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) {
BindParams bindParams = new BindParams();
@@ -107,7 +107,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDeleteChildren(EbeanServer server) {
public SqlUpdate createDeleteChildren(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
public class IntersectionTable {
private final String insertSql;
private final String deleteSql;
private final String draftInsertSql;
private final String draftDeleteSql;
IntersectionTable(String insertSql, String deleteSql, String draftInsertSql, String draftDeleteSql) {
this.insertSql = insertSql;
this.deleteSql = deleteSql;
this.draftInsertSql = draftInsertSql;
this.draftDeleteSql = draftDeleteSql;
}
/**
* Return a SqlUpdate for inserting into the intersection table.
*/
public SqlUpdate insert(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftInsertSql : insertSql);
}
/**
* Return a SqlUpdate for deleting from the intersection table.
*/
public SqlUpdate delete(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftDeleteSql : deleteSql);
}
}
@@ -4,6 +4,7 @@ import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -66,4 +67,14 @@ public interface ImportedId {
* Return the set importedId clause.
*/
String importedIdClause();
/**
* Add DB columns to the intersection builder.
*/
void buildImport(IntersectionBuilder row);
/**
* Bind values to the intersection SqlUpdate.
*/
void bindImport(SqlUpdate sql, EntityBean other);
}
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -130,15 +131,31 @@ public class ImportedIdEmbedded implements ImportedId {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
String msg = "Foreign Key value null?";
throw new PersistenceException(msg);
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
row.put(anImported.localDbColumn, scalarValue);
}
}
@Override
public void buildImport(IntersectionBuilder row) {
for (ImportedIdSimple importedScalar : imported) {
row.addColumn(importedScalar.localDbColumn);
}
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
sql.setNextParameter(scalarValue);
}
}
/**
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanFkeyProperty;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -98,6 +99,20 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
return foreignProperty.getValue(bean);
}
@Override
public void buildImport(IntersectionBuilder row) {
row.addColumn(localDbColumn);
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
Object value = getIdValue(other);
if (value == null) {
throw new PersistenceException("Foreign Key value null?");
}
sql.setNextParameter(value);
}
@Override
public void buildImport(IntersectionRow row, EntityBean other) {
@@ -106,6 +106,11 @@ public class DeployBeanDescriptor<T> {
private PlatformIdGenerator idGenerator;
/**
* Set true when explicit auto generated Id.
*/
private boolean idGeneratedValue;
/**
* The database sequence name (optional).
*/
@@ -848,6 +853,20 @@ public class DeployBeanDescriptor<T> {
}
}
/**
* Return true for automatic Id generation strategy.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Set when GeneratedValue explicitly mapped on Id property.
*/
public void setIdGeneratedValue() {
this.idGeneratedValue = true;
}
/**
* Assign the standard UUID generator.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
@@ -16,7 +17,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
/**
* Persist settings.
*/
protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
/**
* The join table information.
@@ -42,6 +43,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
private int fetchPreference = 1000;
private PropertyForeignKey foreignKey;
/**
* Construct the property.
*/
@@ -117,6 +120,13 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
return cascadeInfo;
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Return the mappedBy deployment attribute.
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.query.SqlJoinType;
import javax.persistence.CascadeType;
@@ -22,8 +21,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
private String columnPrefix;
private PropertyForeignKey foreignKey;
private boolean orphanRemoval;
/**
@@ -148,14 +145,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
}
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public void setOrphanRemoval(boolean orphanRemoval) {
this.orphanRemoval = orphanRemoval;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.PrivateOwned;
@@ -12,6 +13,7 @@ import io.ebean.util.StringHelper;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
@@ -95,6 +97,12 @@ class AnnotationAssocManys extends AnnotationParser {
readElementCollection(prop, elementCollection);
}
// for ManyToMany typically to disable foreign keys from intersection table
DbForeignKey dbForeignKey = get(prop, DbForeignKey.class);
if (dbForeignKey != null){
prop.setForeignKey(new PropertyForeignKey(dbForeignKey));
}
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory();
}
@@ -184,7 +184,7 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation?";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
@@ -322,7 +322,7 @@ public class AnnotationFields extends AnnotationParser {
prop.setDbLength(length.value());
}
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
if (nonNull != null) {
prop.setNullable(false);
}
@@ -389,7 +389,7 @@ public class AnnotationFields extends AnnotationParser {
Set<DbMigration> dbMigration = getAll(prop, DbMigration.class);
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
}
private void addIndex(DeployBeanProperty prop, Index index) {
@@ -515,6 +515,7 @@ public class AnnotationFields extends AnnotationParser {
private void readGenValue(GeneratedValue gen, DeployBeanProperty prop) {
descriptor.setIdGeneratedValue();
String genName = gen.generator();
SequenceGenerator sequenceGenerator = find(prop, SequenceGenerator.class);
@@ -123,11 +123,14 @@ public class DeployInherit {
info.setColumnDefn(da.columnDefinition());
}
DiscriminatorValue dv = AnnotationUtil.findAnnotation(cls, DiscriminatorValue.class); // do not search recursive
if (dv != null) {
info.setDiscriminatorValue(dv.value());
if (!info.isAbstract()) {
DiscriminatorValue dv = AnnotationUtil.findAnnotation(cls, DiscriminatorValue.class); // do not search recursive
if (dv != null) {
info.setDiscriminatorValue(dv.value());
} else {
info.setDiscriminatorValue(cls.getSimpleName());
}
}
return info;
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebeaninternal.server.deploy.InheritInfo;
import java.lang.reflect.Modifier;
import javax.persistence.DiscriminatorType;
import java.sql.Types;
import java.util.ArrayList;
@@ -64,7 +65,7 @@ public class DeployInheritInfo {
* Return true if this is abstract node.
*/
public boolean isAbstract() {
return (discriminatorObjectValue == null);
return Modifier.isAbstract(type.getModifiers());
}
/**
@@ -205,7 +206,7 @@ public class DeployInheritInfo {
}
private void appendDiscriminator(List<Object> list) {
if (discriminatorObjectValue != null) {
if (!isAbstract()) {
list.add(discriminatorObjectValue);
}
for (DeployInheritInfo child : children) {
@@ -174,6 +174,11 @@ public class ElPropertyChain implements ElPropertyValue {
return lastElPropertyValue.isLocalEncrypted();
}
@Override
public Object localEncrypt(Object value) {
return lastElPropertyValue.localEncrypt(value);
}
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
return lastElPropertyValue.getAssocIsEmpty(request, path);
@@ -52,6 +52,11 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
*/
boolean isDbEncrypted();
/**
* Encrypt the input value return the encrypted value.
*/
Object localEncrypt(Object value);
/**
* Return the value ensuring objects prior to the top scalar property are
* automatically populated.
@@ -84,10 +84,11 @@ public class SimpleExpression extends AbstractValueExpression {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindEncryptKey(encryptKey);
} else if (prop.isLocalEncrypted()) {
Object bindVal = prop.localEncrypt(value());
request.addBindEncryptKey(bindVal);
return;
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
// prop.getBeanProperty().getScalarType();
}
request.addBindValue(value());
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
public class DLogManager implements SpiLogManager {
private final SpiLogger sql;
private final SpiLogger summary;
private final SpiLogger txn;
public DLogManager(SpiLogger sql, SpiLogger summary, SpiLogger txn) {
this.sql = sql;
this.summary = summary;
this.txn = txn;
}
@Override
public SpiLogger txn() {
return txn;
}
@Override
public SpiLogger sql() {
return sql;
}
@Override
public SpiLogger sum() {
return summary;
}
}
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import org.slf4j.LoggerFactory;
/**
* Default SpiLoggerFactory implementation.
*/
public class DLoggerFactory implements SpiLoggerFactory {
/**
* Just use a standard slf4j Logger.
*/
@Override
public SpiLogger create(String name) {
return new DSpiLogger(LoggerFactory.getLogger(name));
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import org.slf4j.Logger;
public class DSpiLogger implements SpiLogger {
private final Logger logger;
public DSpiLogger(Logger logger) {
this.logger = logger;
}
@Override
public boolean isDebug() {
return logger.isDebugEnabled();
}
@Override
public boolean isTrace() {
return logger.isTraceEnabled();
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void trace(String msg) {
logger.trace(msg);
}
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -357,4 +358,11 @@ public final class BatchControl {
private BatchedBeanHolder[] getBeanHolderArray() {
return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]);
}
/**
* Execute a batched statement.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
return pstmtHolder.execute(key, getGeneratedKeys);
}
}
@@ -38,6 +38,8 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
private long profileStart;
private int[] results;
/**
* Create with a given statement.
*/
@@ -116,7 +118,7 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
private void executeAndCheckRowCounts() throws SQLException {
int[] results = pstmt.executeBatch();
results = pstmt.executeBatch();
if (results.length != list.size()) {
String s = "results array error " + results.length + " " + list.size();
throw new SQLException(s);
@@ -140,4 +142,10 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
}
}
/**
* Return the execution results (row counts).
*/
public int[] getResults() {
return results;
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.persist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedHashMap;
@@ -73,6 +74,19 @@ public class BatchedPstmtHolder {
return stmtMap.isEmpty();
}
/**
* Execute one of the batched statements returning the row counts.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
BatchedPstmt batchedPstmt = stmtMap.remove(key);
if (batchedPstmt == null) {
throw new PersistenceException("No batched statement found for key " + key);
}
batchedPstmt.executeBatch(getGeneratedKeys);
return batchedPstmt.getResults();
}
/**
* Execute all batched PreparedStatements.
*
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.persist;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
@@ -48,7 +48,7 @@ public class Binder {
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
public Binder(TypeManager typeManager, SpiLogManager logManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) {
this.typeManager = typeManager;
@@ -57,12 +57,7 @@ public class Binder {
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
this.multiValueBind = multiValueBind;
this.enableBindLog = enableBindLog();
}
private boolean enableBindLog() {
return TransactionManager.SQL_LOGGER.isDebugEnabled()
|| TransactionManager.SUM_LOGGER.isDebugEnabled();
this.enableBindLog = logManager.sql().isDebug();
}
/**
@@ -13,6 +13,7 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanPersistController;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdate;
import io.ebeaninternal.server.core.Message;
@@ -34,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -124,13 +126,28 @@ public final class DefaultPersister implements Persister {
}
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
new PersistRequestUpdateSql(server, sqlUpdate, transaction, persistExecute).addBatch();
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
BatchControl batchControl = transaction.getBatchControl();
try {
return batchControl.execute(sqlUpdate.getSql(), sqlUpdate.isGetGeneratedKeys());
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* Execute the updateSql.
*/
@Override
public int executeSqlUpdate(SqlUpdate updSql, Transaction t) {
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
return executeOrQueue(new PersistRequestUpdateSql(server, (SpiSqlUpdate) updSql, (SpiTransaction) t, persistExecute));
}
/**
@@ -588,10 +605,18 @@ public final class DefaultPersister implements Persister {
}
}
private void deleteList(List<?> beanList, Transaction t, boolean softDelete) {
private void deleteList(List<?> beanList, SpiTransaction t, boolean softDelete, boolean children) {
if (children) {
t.depth(-1);
t.checkBatchEscalationOnCollection();
}
for (Object aBeanList : beanList) {
deleteRecurse((EntityBean) aBeanList, t, softDelete);
}
if (children) {
t.flushBatchOnCollection();
t.depth(+1);
}
}
/**
@@ -675,7 +700,7 @@ public final class DefaultPersister implements Persister {
t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values");
}
List<?> beanList = server.findList(q, t);
deleteList(beanList, t, softDelete);
deleteList(beanList, t, softDelete, false);
return beanList.size();
} else {
@@ -1012,7 +1037,7 @@ public final class DefaultPersister implements Persister {
// cascade delete the beans in the collection
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
if (!softDelete || targetDesc.isSoftDelete()) {
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
if (targetDesc.isDeleteByStatement()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
SqlUpdate sqlDelete = intRow.createDelete(server, softDelete);
@@ -1037,13 +1062,13 @@ public final class DefaultPersister implements Persister {
*/
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds, boolean softDelete) {
if (targetDesc.propertiesManyToMany().length > 0) {
if (!targetDesc.isDeleteByBulk()) {
// convert into a list of reference objects and perform delete by object
List<Object> refList = new ArrayList<>(childIds.size());
for (Object id : childIds) {
refList.add(targetDesc.createReference(id, null));
}
deleteList(refList, t, softDelete);
deleteList(refList, t, softDelete, true);
} else {
// perform delete by statement if possible
@@ -26,6 +26,14 @@ class MergeContext {
this.clientGeneratedIds = clientGeneratedIds;
}
public SpiEbeanServer getServer() {
return server;
}
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Add to the list of beans to delete.
*/
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*/
class MergeHandler {
private final Pattern PATH_SPLIT = Pattern.compile("\\.");
private static final Pattern PATH_SPLIT = Pattern.compile("\\.");
private final SpiEbeanServer server;
private final BeanDescriptor<?> desc;
@@ -51,7 +51,7 @@ class MergeHandler {
List<EntityBean> merge() {
Set<String> paths = options.paths();
if (paths.isEmpty() && !options.isClientGeneratedIds()) {
if (desc.isIdGeneratedValue() && paths.isEmpty() && !options.isClientGeneratedIds()) {
// just do a single insert or update based on Id value present
Object id = desc.getId(bean);
if (id != null) {
@@ -139,7 +139,12 @@ class MergeHandler {
throw new PersistenceException("merge path [" + path + "] is not a ToMany or ToOne property of " + targetDesc.getFullName());
}
if (prop instanceof BeanPropertyAssocMany<?>) {
return new MergeNodeAssocMany(fullPath, (BeanPropertyAssocMany<?>) prop);
BeanPropertyAssocMany<?> assocMany = (BeanPropertyAssocMany<?>) prop;
if (assocMany.isManyToMany()) {
return new MergeNodeAssocManyToMany(fullPath, assocMany);
} else {
return new MergeNodeAssocOneToMany(fullPath, assocMany);
}
} else {
return new MergeNodeAssocOne(fullPath, (BeanPropertyAssocOne<?>) prop);
}
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -50,6 +52,22 @@ abstract class MergeNode {
return null;
}
/**
* Return the outline beans as a map keyed by Id values.
*/
Map<Object, EntityBean> toMap(Collection outlines) {
Map<Object, EntityBean> outlineMap = new HashMap<>();
if (outlines != null) {
for (Object out : outlines) {
EntityBean outlineBean = (EntityBean) out;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineMap.put(outlineId, outlineBean);
}
}
return outlineMap;
}
/**
* Add to the query to fetch the Ids values for the foreign keys basically.
*/
@@ -0,0 +1,82 @@
package io.ebeaninternal.server.persist;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.IntersectionTable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Node for processing merge on ManyToMany properties.
*/
class MergeNodeAssocManyToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocManyToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
public void merge(MergeRequest request) {
EntityBean parentBean = request.getBean();
Collection beans = many.getRawCollection(parentBean);
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = toMap(outlines);
List<EntityBean> additions = new ArrayList<>();
if (beans != null) {
for (Object bean : beans) {
EntityBean entityBean = (EntityBean) bean;
Object beanId = targetDescriptor.getId(entityBean);
if (beanId != null) {
if (outlineIds.remove(beanId) == null) {
additions.add(entityBean);
}
}
}
}
// any remaining are considered deletes
List<EntityBean> deletions = new ArrayList<>(outlineIds.values());
SpiEbeanServer server = request.getServer();
SpiTransaction transaction = request.getTransaction();
IntersectionTable intersectionTable = many.intersectionTable();
if (!deletions.isEmpty()) {
transaction.flushBatch();
SqlUpdate delete = intersectionTable.delete(server, false);
for (EntityBean deletion : deletions) {
many.intersectionBind(delete, parentBean, deletion);
delete.addBatch();
}
delete.execute();
}
if (!additions.isEmpty()) {
transaction.flushBatch();
SqlUpdate insert = intersectionTable.insert(server, false);
for (EntityBean addition : additions) {
many.intersectionBind(insert, parentBean, addition);
insert.addBatch();
}
insert.execute();
}
many.resetMany(parentBean);
}
}
@@ -10,11 +10,11 @@ import java.util.Map;
/**
* Node for processing merge on ToMany properties.
*/
class MergeNodeAssocMany extends MergeNode {
class MergeNodeAssocOneToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocMany(String fullPath, BeanPropertyAssocMany<?> property) {
MergeNodeAssocOneToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
@@ -24,14 +24,7 @@ class MergeNodeAssocMany extends MergeNode {
Collection beans = many.getRawCollection(request.getBean());
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = new HashMap<>();
if (outlines != null) {
for (Object outline : outlines) {
EntityBean outlineBean = (EntityBean) outline;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineIds.put(outlineId, outlineBean);
}
}
Map<Object, EntityBean> outlineIds = toMap(outlines);
if (beans != null) {
for (Object bean : beans) {
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.persist;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
/**
* Request object used for processing the merge.
@@ -18,6 +20,20 @@ class MergeRequest {
this.outline = outline;
}
/**
* Return the associated server.
*/
SpiEbeanServer getServer() {
return context.getServer();
}
/**
* Return the associated transaction.
*/
public SpiTransaction getTransaction() {
return context.getTransaction();
}
/**
* Create a sub request with the given beans (to cascade the processing).
*/
@@ -154,9 +154,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
*/
protected void logSql(String sql) {
if (logLevelSql) {
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
}
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
transaction.logSql(sql);
}
}
@@ -187,7 +185,10 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
@Override
public void bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException {
if (logLevelSql) {
bindLog.append(logPlaceHolder).append(" ");
if (bindLog.length() > 0) {
bindLog.append(",");
}
bindLog.append(logPlaceHolder);
}
dataBind.setObject(value, sqlType);
}
@@ -19,7 +19,6 @@ import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,9 +81,7 @@ public class CQueryEngine {
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
}
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
request.logSql(logSql);
}
@@ -151,11 +148,7 @@ public class CQueryEngine {
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
String logSql = sql;
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
request.logSql(logSql);
request.logSql(Str.add(sql, "; --bind(", bindLog, ")"));
}
/**
@@ -458,9 +451,7 @@ public class CQueryEngine {
private void logSql(CQuery<?> query) {
String sql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
}
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
query.getTransaction().logSql(sql);
}
@@ -1276,8 +1276,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public boolean isBeanCacheReload() {
return CacheMode.PUT == useBeanCache;
public boolean isForceHitDatabase() {
return forUpdate != null || CacheMode.PUT == useBeanCache;
}
@Override
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.rawsql;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
import io.ebeaninternal.server.querydefn.SimpleTextParser;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
/**
* Parses sql-select queries to try and determine the location where WHERE and
@@ -55,11 +55,7 @@ class DRawSqlParser {
private Sql parse() {
if (!hasPlaceHolders()) {
// parse the sql for the keywords...
// select, from, where, having, group by, order by
parseSqlFindKeywords(true);
}
parseSqlFindKeywords(true);
whereExprPos = findWhereExprPosition();
havingExprPos = findHavingExprPosition();
@@ -43,6 +43,9 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private final TransactionManager manager;
private final boolean logSql;
private final boolean logSummary;
/**
* The status of the transaction.
*/
@@ -70,6 +73,8 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
this.manager = manager;
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.active = true;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
@@ -231,6 +236,16 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
public void markNotQueryOnly() {
}
@Override
public boolean isNestedUseSavepoint() {
return false;
}
@Override
public void setNestedUseSavepoint() {
}
@Override
public boolean isReadOnly() {
if (!isActive()) {
@@ -431,22 +446,22 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(msg);
manager.log().sql().debug(msg);
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(msg);
manager.log().sum().debug(msg);
}
/**
@@ -55,6 +55,9 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
protected final String id;
private final boolean logSql;
private final boolean logSummary;
/**
* The user defined label to group execution statistics.
*/
@@ -77,6 +80,8 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected boolean rollbackOnly;
protected boolean nestedUseSavepoint;
/**
* The underlying Connection.
*/
@@ -198,11 +203,15 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.startNanos = System.nanoTime();
if (manager == null) {
this.logSql = false;
this.logSummary = false;
this.skipCacheAfterWrite = true;
this.batchMode = PersistBatch.NONE;
this.batchOnCascadeMode = PersistBatch.NONE;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
this.batchMode = manager.getPersistBatch();
this.batchOnCascadeMode = manager.getPersistBatchOnCascade();
@@ -859,22 +868,22 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sql().debug(Str.add(logPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sum().debug(Str.add(logPrefix, msg));
}
/**
@@ -1117,6 +1126,16 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.rollbackOnly = true;
}
@Override
public boolean isNestedUseSavepoint() {
return nestedUseSavepoint;
}
@Override
public void setNestedUseSavepoint() {
this.nestedUseSavepoint = true;
}
/**
* Rollback the transaction.
*/
@@ -37,6 +37,16 @@ class NoTransaction implements SpiTransaction {
return null;
}
@Override
public boolean isNestedUseSavepoint() {
return false;
}
@Override
public void setNestedUseSavepoint() {
}
@Override
public boolean isActive() {
// always false
@@ -0,0 +1,109 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionProxy;
import io.ebeaninternal.server.lib.util.Str;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;
/**
* A nested transaction that represents a Savepoint.
*/
class SavepointTransaction extends SpiTransactionProxy {
private static final int STATE_COMMITTED = 1;
private static final int STATE_ROLLED_BACK = 2;
private final TransactionManager manager;
private final Savepoint savepoint;
private final Connection connection;
private final String logPrefix;
private final String spPrefix;
private boolean rollbackOnly;
private int state;
SavepointTransaction(SpiTransaction transaction, TransactionManager manager) throws SQLException {
this.manager = manager;
this.transaction = transaction;
this.connection = transaction.getInternalConnection();
this.savepoint = connection.setSavepoint();
int savepointId = savepoint.getSavepointId();
this.spPrefix = "sp[" + savepointId + "] ";
this.logPrefix = transaction.getLogPrefix() + spPrefix;
}
@Override
public String getLogPrefix() {
return logPrefix;
}
@Override
public void logSql(String msg) {
transaction.logSql(Str.add(spPrefix, msg));
}
@Override
public void logSummary(String msg) {
transaction.logSummary(Str.add(spPrefix, msg));
}
@Override
public void setRollbackOnly() {
this.rollbackOnly = true;
}
@Override
public void commit() {
if (rollbackOnly) {
rollbackSavepoint(null);
} else {
commitSavepoint();
}
}
@Override
public void rollback() throws PersistenceException {
rollbackSavepoint(null);
}
@Override
public void rollback(Throwable e) throws PersistenceException {
rollbackSavepoint(e);
}
private void commitSavepoint() {
try {
connection.releaseSavepoint(savepoint);
state = STATE_COMMITTED;
manager.notifyOfCommit(this);
} catch (SQLException e) {
throw new PersistenceException("Error trying to commit/release Savepoint", e);
}
}
private void rollbackSavepoint(Throwable cause) throws PersistenceException {
try {
connection.rollback(savepoint);
state = STATE_ROLLED_BACK;
manager.notifyOfRollback(this, cause);
} catch (SQLException e) {
throw new PersistenceException("Error trying to rollback Savepoint", e);
}
}
@Override
public void end() {
if (state == 0) {
rollback();
}
}
@Override
public void close() {
end();
}
}
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiTransaction;
import org.slf4j.Logger;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -14,8 +13,6 @@ import java.util.concurrent.atomic.AtomicLong;
*/
abstract class TransactionFactory {
static final Logger TXN_LOGGER = TransactionManager.TXN_LOGGER;
final AtomicLong counter = new AtomicLong(1000);
final TransactionManager manager;
@@ -51,8 +48,8 @@ abstract class TransactionFactory {
}
}
if (explicit && TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(t.getLogPrefix() + "Begin");
if (explicit && manager.log().txn().isTrace()) {
manager.log().txn().trace(t.getLogPrefix() + "Begin");
}
return t;
@@ -15,6 +15,8 @@ import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
@@ -53,12 +55,6 @@ public class TransactionManager implements SpiTransactionManager {
public static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
public static final Logger SQL_LOGGER = LoggerFactory.getLogger("io.ebean.SQL");
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("io.ebean.SUM");
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("io.ebean.TXN");
protected final BeanDescriptorManager beanDescriptorManager;
/**
@@ -128,6 +124,9 @@ public class TransactionManager implements SpiTransactionManager {
private final TransactionFactory transactionFactory;
private final SpiLogManager logManager;
private final SpiLogger txnLogger;
private final DatabasePlatform databasePlatform;
private final SpiProfileHandler profileHandler;
@@ -142,6 +141,8 @@ public class TransactionManager implements SpiTransactionManager {
*/
public TransactionManager(TransactionManagerOptions options) {
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
@@ -355,12 +356,12 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
if (txnLogger.isDebug()) {
String msg = transaction.getLogPrefix() + "Rollback";
if (cause != null) {
msg += " error: " + formatThrowable(cause);
}
TXN_LOGGER.debug(msg);
txnLogger.debug(msg);
}
} catch (Exception ex) {
@@ -374,8 +375,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfQueryOnly(SpiTransaction transaction) {
// Nothing that interesting here
if (TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(transaction.getLogPrefix() + "Commit - query only");
if (txnLogger.isTrace()) {
txnLogger.trace(transaction.getLogPrefix() + "Commit - query only");
}
}
@@ -409,8 +410,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfCommit(SpiTransaction transaction) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit");
if (txnLogger.isDebug()) {
txnLogger.debug(transaction.getLogPrefix() + "Commit");
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
@@ -582,27 +583,42 @@ public class TransactionManager implements SpiTransactionManager {
txScope = initTxScope(txScope);
boolean setToScope = false;
ScopedTransaction txnContainer = getActiveScoped();
boolean setToScope;
boolean nestedSavepoint;
SpiTransaction transaction;
if (txnContainer == null) {
setToScope = true;
txnContainer = createScopedTransaction();
setToScope = true;
transaction = null;
nestedSavepoint = false;
} else {
setToScope = false;
transaction = txnContainer.current();
nestedSavepoint = transaction.isNestedUseSavepoint();
}
SpiTransaction transaction = txnContainer.current();
TxType type = txScope.getType();
boolean createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = createTransaction(true, txScope.getIsolationLevel());
initNewTransaction(transaction, txScope);
boolean createTransaction;
if (nestedSavepoint && (type == TxType.REQUIRED || type == TxType.REQUIRES_NEW)) {
createTransaction = true;
transaction = createSavepoint(transaction, this);
} else {
createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = createTransaction(true, txScope.getIsolationLevel());
initNewTransaction(transaction, txScope);
}
}
}
@@ -613,6 +629,14 @@ public class TransactionManager implements SpiTransactionManager {
return txnContainer;
}
private SpiTransaction createSavepoint(SpiTransaction transaction, TransactionManager manager) {
try {
return new SavepointTransaction(transaction, manager);
} catch (SQLException e) {
throw new PersistenceException("Error creating nested Savepoint Transaction", e);
}
}
private void initNewTransaction(SpiTransaction transaction, TxScope txScope) {
if (txScope.isSkipCache()) {
@@ -679,4 +703,16 @@ public class TransactionManager implements SpiTransactionManager {
throw new RuntimeException("Should never get here?");
}
}
public SpiLogManager log() {
return logManager;
}
public boolean isLogSql() {
return logManager.sql().isDebug();
}
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
}

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