Compare commits

..
Author SHA1 Message Date
Rob Bygrave 49b568e7d4 [maven-release-plugin] prepare release ebean-11.10.5 2018-02-15 22:32:43 +13:00
Rob Bygrave 628af31ddd #1142 - Mapping - Add support for @OrderColumn (Ebean automatically maintains an order column) 2018-02-15 22:31:41 +13:00
Rob Bygrave 31cac60393 #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns
Adds Bi-directional support
2018-02-15 00:49:51 +13:00
Rob Bygrave 6a067c0cf7 #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns
Extra test with UUID and optional OneToOne so has left join
2018-02-14 23:09:10 +13:00
Rob Bygrave 842292ef6b #107 - Mapping - Add support for @PrimaryKeyJoinColumn/@PrimaryKeyJoinColumns 2018-02-14 22:59:57 +13:00
Rob Bygrave 6db9a60f4f #107 - Fix existing test - Ah ??? ... this PrimaryKeyJoinColumn was ignored and should never have been in this test. 2018-02-14 17:24:14 +13:00
Rob Bygrave d9125db957 [maven-release-plugin] prepare for next development iteration 2018-02-14 15:29:26 +13:00
Rob Bygrave 4bd9e67703 [maven-release-plugin] prepare release ebean-11.10.4 2018-02-14 15:29:12 +13:00
Rob Bygrave 2a986ab590 #1260 - Mark query.setForUpdate(true) as deprecated - migrate to query.forUpdate() 2018-02-14 15:28:13 +13:00
Rob Bygrave 7c477827fe #1259 - Throw explicit mapping error when an Enum is mapped to both ORDINAL and STRING 2018-02-14 12:39:35 +13:00
Rob Bygrave b2967dec1e #1257 - ServerConfig with register = true, defaultServer = false ... fails as it tries to create the default server 2018-02-13 16:22:52 +13:00
Rob Bygrave a95fd063ae #1256 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL (Part 2) 2018-02-13 14:52:18 +13:00
Rob Bygrave 17a54e3804 No effective change - extra tests for DeployPropertyParser 2018-02-13 14:09:08 +13:00
Rob Bygrave f1b2a09b30 Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:16:40 +13:00
Rob Bygrave e4d9e4f2de Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:04:19 +13:00
Rob Bygrave 6474f306b5 #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:57:19 +13:00
Rob Bygrave f1c2f134ad #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:56:05 +13:00
Rob Bygrave 0f7158ea18 Testing - modify .travis to run mysql as service 2018-02-10 00:34:34 +13:00
Rob Bygrave 3fdb76d18f Testing - modify .travis to run mysql as service 2018-02-10 00:31:39 +13:00
Rob Bygrave b1027a2f25 Testing - modify .travis to run mysql as docker container 2018-02-10 00:18:33 +13:00
Rob Bygrave e5a1f9f7cc Testing - modify .travis adding docker service 2018-02-10 00:07:27 +13:00
Rob Bygrave f78f24136a [maven-release-plugin] prepare for next development iteration 2018-02-09 23:42:42 +13:00
Rob Bygrave 5b2aa0c74d [maven-release-plugin] prepare release ebean-11.10.3 2018-02-09 23:42:28 +13:00
Rob Bygrave 175714046a #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Yeah, nah. This is the real fix. Expose:

   SpiTransaction TransctionManager.getInScope();

... and have ebean-spring-txn SpringJdbcTransactionManager call it instead of getActive().
2018-02-09 23:03:07 +13:00
Rob Bygrave 73b2cc55f3 #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Fix. Note tested in ebean-spring-txn.
2018-02-09 22:22:08 +13:00
Rob Bygrave cebe375856 No effective change - initial docker testing setup for oracle and sqlserver 2018-02-09 21:31:40 +13:00
Rob Bygrave 2f47235018 #1255 - Regression on raw LIKE expression with slashes on Postgres 2018-02-09 21:28:39 +13:00
Rob Bygrave 17abd6d977 #1255 - Regression on raw LIKE expression with slashes on Postgres
Tests
2018-02-09 21:25:36 +13:00
Rob Bygrave cc5f3689f3 No effective change - modify tests for SqlServer 2018-02-09 21:10:18 +13:00
Rob Bygrave abcc651e7f #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Fix
2018-02-09 15:00:58 +13:00
Rob Bygrave caa36262c1 #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Failing test
2018-02-09 15:00:29 +13:00
Rob Bygrave 66b6dba921 [maven-release-plugin] prepare for next development iteration 2018-02-07 16:54:44 +13:00
Rob Bygrave fc01f230f3 [maven-release-plugin] prepare release ebean-11.10.2 2018-02-07 16:54:31 +13:00
Rob Bygrave 7fe3e657d7 No effective change - put default test db back to default to h2 2018-02-07 16:49:49 +13:00
Rob Bygrave 9bade12c9d #1251 - Delete cascade with 11+ and Postgres throws exception
Fix
2018-02-07 16:47:59 +13:00
Rob Bygrave 7a816ae2da #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:40:12 +13:00
Rob Bygrave b83412281e #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:39:58 +13:00
Rob Bygrave 478bba292d #1250 - Add explicit coverage for Enum with abstract method - no new issue 2018-02-07 14:01:11 +13:00
Rob Bygrave 77a4e684bb [maven-release-plugin] prepare for next development iteration 2018-02-02 23:39:51 +13:00
Rob Bygrave a8254c4295 [maven-release-plugin] prepare release ebean-11.10.1 2018-02-02 23:39:38 +13:00
Rob Bygrave 2a5d0084ef #1249 - Bump db-migration to 11.3.1 ... with fix for not running empty sql statements (empty strings) 2018-02-02 23:38:11 +13:00
Rob Bygrave f9404639d7 Fix test for H2 select for update - my bad. 2018-02-02 23:36:12 +13:00
Rob Bygrave bbef5a6bbb #1243 - Add IDENTITY support for Oracle platform (since 12c Oracle supports auto generated identity) 2018-02-02 23:18:23 +13:00
Rob Bygrave 4645fd9725 Cleanup tests for Oracle 2018-02-02 23:17:21 +13:00
Rob Bygrave 823cb45221 Cleanup tests for Oracle 2018-02-02 23:17:03 +13:00
Rob Bygrave b2bbb05b83 #1248 - ENH: Additionally read "db" system property to determine default database (to use when testing with docker against different databases) 2018-02-02 20:49:39 +13:00
Rob Bygrave 5ff71324d0 #1247 - Unique index or primary key violation - when jdbc batch cascading to PrivateOwned child with unique constraint 2018-02-02 20:43:55 +13:00
Rob Bygrave 851f1c98b0 #1245 - Refactor internals - move Transaction ProfileStream creation
Stops the profileId parameter pollution, enables to potentially use transaction label
and runtime toggling of collecting transaction profiling detail.
2018-01-31 14:02:50 +13:00
Rob Bygrave a92e620f7e #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 13:36:23 +13:00
Rob Bygrave f9cd955f7a #1245 - Refactor internals - Add to TransactionManager externalRemoveTransaction() and externalBeginTransaction()
These provide a better/tidier way for external transaction managers (like ebean-spring-txn) to begin and end externally
managed transactions.
2018-01-31 13:28:42 +13:00
Rob Bygrave 71f66005e7 Merge branch 'master' of github.com:ebean-orm/ebean 2018-01-31 11:57:10 +13:00
Rob Bygrave b682293a32 #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 11:56:38 +13:00
Rob Bygrave 5bd7853ce8 #1245 - Refactor internals - move TransactionMangerScope to be part of TransactionManager 2018-01-31 11:30:11 +13:00
André CamiloandRob Bygrave 276c56161a DiscriminatorColumn length should be 31 (#1244) 2018-01-31 08:32:21 +13:00
Rob Bygrave 6b01821623 #1242 - Bump avaje-classpath-scanner dependency to 3.1.1 2018-01-26 11:51:28 +13:00
Rob Bygrave 8495ccced6 #1241 - ENH: Support using ebean_db system property to control docker container for testing 2018-01-24 19:17:14 +13:00
Rob Bygrave 2d929837c4 No effective change - Update testing for profile location enhancement (#1227) 2018-01-24 19:11:59 +13:00
Rob Bygrave 6754992b1c [maven-release-plugin] prepare for next development iteration 2018-01-22 23:02:52 +13:00
Rob Bygrave 08b7f4a95f [maven-release-plugin] prepare release ebean-11.9.1 2018-01-22 23:02:37 +13:00
Rob Bygrave 07e2605a30 #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 22:57:35 +13:00
Rob Bygrave 9a0b742f14 #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 15:07:50 +13:00
Rob Bygrave bce745e88d #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 15:04:13 +13:00
Rob Bygrave 51d2e4c600 ENH: Add ProfileLocation - Part 2 - performance metrics for transactions with label and profileLocation 2018-01-22 14:54:27 +13:00
Rob Bygrave 4e33027fdf Bump to 11.9.1-SNAPSHOT, add test dependency for logback 2018-01-19 11:10:18 +13:00
Rob Bygrave 0a809b0fb3 #1239 - When Transaction.setSkipCache(true) ... Query with useQueryCache is still use L2 query cache 2018-01-19 10:46:49 +13:00
Rob Bygrave 13ddb915c8 No effective change - add javadoc 2018-01-19 09:38:50 +13:00
Rob Bygrave 07cdf64ece #1238 - Refactor MetaInfoManager - collect query plan stats, fix collection of stats on UpdateQuery 2018-01-19 01:07:37 +13:00
Rob Bygrave b9ae76d465 #1237 - ENH: Add ProfileLocation - support for relating performance metrics for queries and transactions back to source line of code 2018-01-18 18:10:03 +13:00
Rob Bygrave 82f5c21d26 No effective change - Improve exception error message when property is not found on a bean type
Typically using IDE enhancement plugin with incremental compilation where part of an inheritance
hierarchy has been changed but not all related classes recompiled and re-enhanced.
2018-01-12 01:27:24 +13:00
Rob Bygrave 14105eb1eb #1236 - ENH: Add Transaction.current() ... as alternative to obtain the current transaction 2018-01-11 22:34:31 +13:00
Rob Bygrave bab27f25ca [maven-release-plugin] prepare for next development iteration 2018-01-09 23:19:09 +13:00
Rob Bygrave eed8d69715 [maven-release-plugin] prepare release ebean-11.8.1 2018-01-09 23:18:54 +13:00
Rob Bygrave 7b93df2df7 Bump pom version to 11.8.1-SNAPSHOT 2018-01-09 23:17:53 +13:00
Rob Bygrave bb205ced54 #1235 - Thread deadlock on L2 cache load of many property 2018-01-09 23:16:35 +13:00
Rob Bygrave 0d4c50e19c #1234 - Remove deprecated CacheMode.QUERY_ONLY (migrate to CacheMode.GET) 2018-01-09 19:56:55 +13:00
Rob Bygrave 8cff85fdda #1233 - Remove deprecated CacheMode.RECACHE (migrate to CacheMode.PUT) 2018-01-09 19:50:26 +13:00
Rob Bygrave 932986a6b5 #1232 - Performance: Add support for a second read-only DataSource (for implicit query-only transactions) 2018-01-09 16:35:22 +13:00
Rob Bygrave 01539091ef #1231 - Performance: Only use CallStack when using AutoTune (automatic query tuning or profiling) 2018-01-09 13:28:57 +13:00
Rob Bygrave 7e69f71b51 #1230 - Add DB Migration non strict mode - IllegalArgumentException: non-null column has no default value 2018-01-07 15:33:18 +13:00
Rob Bygrave 58d6b2c24e Merge branch 'master' of github.com:ebean-orm/ebean 2018-01-06 12:52:38 +13:00
Rob Bygrave 3c7ef7d46e #1229 - Implement equals and hashCode on ModifyAwareMap, ModifyAwareList and ModifyAwareSet 2018-01-06 12:51:50 +13:00
Rob Bygrave db5db26fca No effective change - rename parameter fix typo 2018-01-06 12:21:14 +13:00
rob bygrave 7110f4a59b No effective change - remove unused HashQueryPlan etc 2017-12-28 16:17:00 +13:00
rob bygrave 9f91d8bcf9 No effective change - modify specific test to run with Postgres specific syntax 2017-12-27 23:23:01 +13:00
Rob Bygrave ac97444ad1 No effective change - add test for @Aggregation with order by clause 2017-12-27 13:49:46 +13:00
Rob Bygrave f6e9afcab6 #1226 - Version range in dependency makes sbt resolve very slow. Change slf4j-api dependency to provided scope.
Part 2, bring in new version of avaje-classpath-scanner which also has slf4j-api as provided scope.
2017-12-27 13:48:22 +13:00
Rob Bygrave a7226079fe [maven-release-plugin] prepare for next development iteration 2017-12-14 00:00:04 +13:00
Rob Bygrave fc915010d9 [maven-release-plugin] prepare release ebean-11.7.1 2017-12-13 23:59:49 +13:00
Rob Bygrave c1818bc309 #1226 - Version range in dependency makes sbt resolve very slow. Change slf4j-api dependency to provided scope. 2017-12-13 22:51:23 +13:00
Rob Bygrave 4a4134ee93 #1227 - NPE when using L2 Query cache with findOne() or findOneOrEmpty() 2017-12-13 22:19:25 +13:00
Rob Bygrave e07bd8f736 #1223 - Support mapping @DbArray to a List or Set of enums 2017-12-13 21:15:19 +13:00
Rob Bygrave 4d0b29624e #1222 - Extend L2 ServerCache API with putAll(), getAll() and removeAll() methods 2017-12-13 19:58:28 +13:00
Rob Bygrave c7f4b56dd1 #1225 - ENH: Extend L2 server cache (near caching) for clearing "near caches" 2017-12-13 02:09:10 +13:00
Rob Bygrave 69c045443c #1218 - ENH: Bump to migration 11.2.1 with support for options patchInsertOn and patchResetChecksumOn 2017-11-23 19:05:35 +13:00
Rob Bygrave fd2d083971 #1217 - L2 query cache not caching empty result set 2017-11-22 22:03:35 +13:00
Rob Bygrave b9380aa5b8 No effective change - add property test for ServerConfig jsonDateTime 2017-11-22 21:21:09 +13:00
Rob Bygrave 3a1368a342 [maven-release-plugin] prepare for next development iteration 2017-11-19 20:34:54 +13:00
264 changed files with 7298 additions and 1576 deletions
+11
View File
@@ -9,16 +9,27 @@ git:
addons:
postgresql: "9.6"
# apt:
# packages:
# - mysql-server-5.7
# - mysql-client-core-5.7
# - mysql-client-5.7
services:
- docker
- postgresql
- mysql
before_script:
- ./.travis/setup_database
- mysql -u root -e 'CREATE DATABASE test_ebean;'
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
env:
- EBEAN_DB=h2
- EBEAN_DB=pg
# - EBEAN_DB=mysql
install: true
+33 -26
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.6.1</version>
<version>11.10.5</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.6.1</tag>
<tag>ebean-11.10.5</tag>
</scm>
<profiles>
@@ -72,6 +72,17 @@
</profiles>
<dependencies>
<!--
Projects are expected to explicit depend on version
of slf4j that they want to use
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<!--
Class retention Nonnull and Nullable annotations
to assist with IDE auto-completion with Ebean API
@@ -92,43 +103,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource-api</artifactId>
<version>1.1</version>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.2</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.4</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.1.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>[1.7.1,1.7.99)</version>
<version>11.3.1</version>
</dependency>
<dependency>
@@ -205,6 +198,20 @@
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-docker-run</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -215,7 +222,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.5.1</version>
<version>11.9.1</version>
<scope>test</scope>
</dependency>
@@ -288,7 +295,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.5.1</version>
<version>11.9.1</version>
<executions>
<execution>
<id>test</id>
+7 -7
View File
@@ -56,18 +56,18 @@ public enum CacheMode {
AUTO(true, true),
/**
* Do not read from cache, but load retrieved beans into the cache.
* Do not read from cache, but put beans into the cache and invalidate parts of the cache as necessary.
* <p>
* Use this, if you want to get the fresh value from database into the cache. Typically a CacheMode.ON query
* will follow.
* Use this on a query if you want to get the fresh value from database and put it into the cache.
*/
RECACHE(false, true),
PUT(false, true),
/**
* Query the cache for value. If it is there, use it and otherwise hit database but do NOT put the value
* into the cache. Note that there are not many use case for this mode.
* GET only from the cache.
* <p>
* This mode does not put entries into the cache or invalidate parts of the cache.
*/
QUERY_ONLY(true, false);
GET(true, false);
private boolean get;
private boolean put;
+3 -11
View File
@@ -5,10 +5,10 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
@@ -131,8 +131,7 @@ public final class Ebean {
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
/**
* Cache for synchronized read, creation and put. Protected by the monitor
* object.
* Cache for synchronized read, creation and put. Protected by the monitor object.
*/
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
@@ -146,14 +145,7 @@ public final class Ebean {
private ServerManager() {
try {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (PrimaryServer.isSkip()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("PrimaryServer.isSkip()");
} else {
if (!PrimaryServer.isSkip()) {
// look to see if there is a default server defined
String defaultName = PrimaryServer.getDefaultServerName();
logger.debug("defaultName:{}", defaultName);
@@ -65,10 +65,8 @@ public class EbeanServerFactory {
EbeanServer server = createInternal(config);
if (config.isDefaultServer()) {
PrimaryServer.setSkip(true);
}
if (config.isRegister()) {
PrimaryServer.setSkip(true);
Ebean.register(server, config.isDefaultServer());
}
@@ -59,6 +59,8 @@ class PrimaryServer {
private static String determineDefaultServerName() {
String defaultServerName = System.getenv("EBEAN_DB");
defaultServerName = System.getProperty("db", defaultServerName);
defaultServerName = System.getProperty("ebean_db", defaultServerName);
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
@@ -74,6 +76,7 @@ class PrimaryServer {
if (defaultServerName == null) {
defaultServerName = "db";
}
System.setProperty("ebean_db", defaultServerName);
return defaultServerName;
}
@@ -0,0 +1,47 @@
package io.ebean;
/**
* A location for profiling transactions and queries.
* <p>
* Typically represents a class method in the form of class file and line of code that started
* the transaction or invoked the query.
* </p>
*/
public interface ProfileLocation {
/**
* Create and return a new ProfileLocation.
*/
static ProfileLocation create() {
return XServiceProvider.profileLocationFactory().create();
}
/**
* Create and return a new ProfileLocation with a given lineNumber and label.
*/
static ProfileLocation create(int lineNumber, String label) {
return XServiceProvider.profileLocationFactory().create(lineNumber, label);
}
/**
* Create and return a new ProfileLocation with a given location.
*/
static ProfileLocation createAt(String location) {
return XServiceProvider.profileLocationFactory().createAt(location);
}
/**
* Obtain the location description.
*/
String obtain();
/**
* Return a short version of the location description.
*/
String shortDescription();
/**
* Add execution time.
*/
void add(long executionTime);
}
+9
View File
@@ -1369,6 +1369,12 @@ public interface Query<T> {
*/
Query<T> setProfileId(int profileId);
/**
* Set the profile location of this query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
Query<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set to true if this query should execute against the doc store.
* <p>
@@ -1430,9 +1436,12 @@ public interface Query<T> {
String getGeneratedSql();
/**
* Deprecated - migrate to forUpdate().
*
* executed the select with "for update" which should lock the record
* "on read"
*/
@Deprecated
Query<T> setForUpdate(boolean forUpdate);
/**
+3 -3
View File
@@ -21,7 +21,7 @@ public interface RawSqlBuilder {
* resultSet.
*/
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return XServiceProvider.get().resultSet(resultSet, propertyNames);
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
}
/**
@@ -30,7 +30,7 @@ public interface RawSqlBuilder {
* this query.
*/
static RawSqlBuilder unparsed(String sql) {
return XServiceProvider.get().unparsed(sql);
return XServiceProvider.rawSql().unparsed(sql);
}
/**
@@ -47,7 +47,7 @@ public interface RawSqlBuilder {
* </p>
*/
static RawSqlBuilder parse(String sql) {
return XServiceProvider.get().parsed(sql);
return XServiceProvider.rawSql().parsed(sql);
}
/**
+25
View File
@@ -13,6 +13,23 @@ import java.sql.Connection;
*/
public interface Transaction extends AutoCloseable {
/**
* Return the current transaction (of the default server) or null if there is
* no current transaction in scope.
* <p>
* This is the same as <code>Ebean.currentTransaction()</code>
* </p>
* <p>
* 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()
*/
static Transaction current() {
return Ebean.currentTransaction();
}
/**
* Read Committed transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
@@ -42,6 +59,14 @@ public interface Transaction extends AutoCloseable {
*/
void register(TransactionCallback callback);
/**
* Set a label on the transaction.
* <p>
* This label is used to group transaction execution times for performance metrics reporting.
* </p>
*/
void setLabel(String label);
/**
* Return true if this transaction is read only.
*/
+51
View File
@@ -48,10 +48,16 @@ public final class TxScope {
*/
private boolean flushOnQuery = true;
private boolean skipCache;
private String label;
private ArrayList<Class<? extends Throwable>> rollbackFor;
private ArrayList<Class<? extends Throwable>> noRollbackFor;
private ProfileLocation profileLocation;
/**
* Helper method to create a TxScope with REQUIRES.
*/
@@ -185,6 +191,51 @@ public final class TxScope {
return this;
}
/**
* Return the profile location.
*/
public ProfileLocation getProfileLocation() {
return profileLocation;
}
/**
* Set the profile location.
*/
public TxScope setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
/**
* Return true if the L2 cache should be skipped for this transaction.
*/
public boolean isSkipCache() {
return skipCache;
}
/**
* Set to true if the transaction should skip L2 cache access.
*/
public TxScope setSkipCache(boolean skipCache) {
this.skipCache = skipCache;
return this;
}
/**
* Return the label for the transaction.
*/
public String getLabel() {
return label;
}
/**
* Set a label for the transaction.
*/
public TxScope setLabel(String label) {
this.label = label;
return this;
}
/**
* Return the batch mode.
*/
+6
View File
@@ -148,6 +148,12 @@ public interface UpdateQuery<T> {
*/
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
/**
* Set the profile location of this update query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Return the query expression list to add predicates to.
*/
+24 -4
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
import java.util.Iterator;
@@ -10,9 +11,11 @@ import java.util.ServiceLoader;
*/
class XServiceProvider {
private static SpiRawSqlService builder = init();
private static SpiRawSqlService rawSqlService = initRawSql();
private static SpiRawSqlService init() {
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
private static SpiRawSqlService initRawSql() {
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
if (loader.hasNext()) {
@@ -21,10 +24,27 @@ class XServiceProvider {
throw new IllegalStateException("No service implementation found for SpiRawSqlService?");
}
private static SpiProfileLocationFactory initProfileLocation() {
Iterator<SpiProfileLocationFactory> loader = ServiceLoader.load(SpiProfileLocationFactory.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiProfileLocationFactory?");
}
/**
* Return the RawSqlService implementation.
*/
static SpiRawSqlService get() {
return builder;
static SpiRawSqlService rawSql() {
return rawSqlService;
}
/**
* Return the RawSqlService implementation.
*/
static SpiProfileLocationFactory profileLocationFactory() {
return profileLocationFactory;
}
}
@@ -227,6 +227,11 @@ public interface BeanCollection<E> extends Serializable {
*/
void modifyReset();
/**
* Has been modified by an addition or removal.
*/
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
*/
+8 -2
View File
@@ -26,14 +26,16 @@ public final class CallStack implements Serializable {
private final StackTraceElement[] callStack;
private final int hc;
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
this.callStack = callStack;
this.zeroHash = enc(zeroHash);
this.pathHash = enc(pathHash);
this.hc = computeHashCode();
}
@Override
public int hashCode() {
private int computeHashCode() {
int hc = 0;
for (StackTraceElement aCallStack : callStack) {
hc = 92821 * hc + aCallStack.hashCode();
@@ -41,6 +43,10 @@ public final class CallStack implements Serializable {
return hc;
}
public int hashCode() {
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
@@ -13,8 +13,14 @@ import java.io.Serializable;
*/
public interface EntityBean extends Serializable {
/**
* Return all the property names in defined order.
*/
String[] _ebean_getPropertyNames();
/**
* Return the property name at the given position.
*/
String _ebean_getPropertyName(int pos);
/**
@@ -90,6 +90,7 @@ public final class EntityBeanIntercept implements Serializable {
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
/**
* Create a intercept with a given entity.
@@ -693,6 +694,9 @@ public final class EntityBeanIntercept implements Serializable {
* Add and return a dirty property hash recursing into embedded beans.
*/
private void addDirtyPropertyKey(StringBuilder sb) {
if (sortOrder > 0) {
sb.append("s,");
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
@@ -1050,4 +1054,18 @@ public final class EntityBeanIntercept implements Serializable {
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
/**
* Return the sort order value for an order column.
*/
public int getSortOrder() {
return sortOrder;
}
/**
* Set the sort order value for an order column.
*/
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
}
+18 -4
View File
@@ -17,9 +17,9 @@ import java.util.Set;
*/
public interface ServerCache {
default Map<Object,Object> getAll(Set<Object> keys){
default Map<Object, Object> getAll(Set<Object> keys) {
Map<Object,Object> map = new LinkedHashMap<>();
Map<Object, Object> map = new LinkedHashMap<>();
for (Object key : keys) {
Object value = get(key);
if (value != null) {
@@ -34,15 +34,29 @@ public interface ServerCache {
*/
Object get(Object id);
/**
* Put all the values in the cache.
*/
default void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
}
/**
* Put the value in the cache with a given id.
*/
Object put(Object id, Object value);
void put(Object id, Object value);
/**
* Remove the entries from the cache given the id values.
*/
default void removeAll(Set<Object> keys) {
keys.forEach(this::remove);
}
/**
* Remove a entry from the cache given its id.
*/
Object remove(Object id);
void remove(Object id);
/**
* Clear all entries from the cache.
+15
View File
@@ -44,4 +44,19 @@ public interface ServerCacheManager {
*/
void clearAll();
/**
* Clear all the local caches.
*
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearAllLocal();
/**
* Clear the local caches for this bean type.
*
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearLocal(Class<?> beanType);
}
@@ -213,7 +213,12 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@Override
public boolean wasTouched() {
return modifyHolder != null && modifyHolder.wasTouched();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
@@ -28,7 +28,10 @@ class ModifyHolder<E> implements Serializable {
*/
private Set<E> modifyAdditions = new LinkedHashSet<>();
private boolean touched;
void reset() {
touched = false;
modifyDeletions = new LinkedHashSet<>();
modifyAdditions = new LinkedHashSet<>();
}
@@ -50,6 +53,7 @@ class ModifyHolder<E> implements Serializable {
void modifyAddition(E bean) {
if (bean != null) {
touched = true;
// If it is to delete then just remove the deletion
if (!undoDeletion(bean)) {
// Insert
@@ -65,6 +69,7 @@ class ModifyHolder<E> implements Serializable {
@SuppressWarnings("unchecked")
void modifyRemoval(Object bean) {
if (bean != null) {
touched = true;
// If it is to be added then just remove the addition
if (!undoAddition(bean)) {
modifyDeletions.add((E) bean);
@@ -80,6 +85,14 @@ class ModifyHolder<E> implements Serializable {
return modifyDeletions;
}
/**
* Return true if the collection was touched in some way. This is still true even if
* a bean was removed and then added (which is not held as a modification).
*/
boolean wasTouched() {
return touched;
}
/**
* Return true if there additions or removals.
*/
@@ -32,6 +32,15 @@ public class AutoTuneConfig {
public AutoTuneConfig() {
}
/**
* Return true if we are profiling or query tuning.
*
* If we are not doing either then we don't need a CallStack.
*/
public boolean isActive() {
return profiling || queryTuning;
}
/**
* Return the name of the file that holds the query tuning information.
*/
@@ -10,22 +10,82 @@ import java.util.Properties;
*/
public class ContainerConfig {
protected boolean clusterActive;
private boolean active;
private String serviceName;
private String namespace;
private String podName;
private int port;
protected Properties properties;
private Properties properties;
/**
* Return the service name.
*/
public String getServiceName() {
return serviceName;
}
/**
* Set the service name.
*/
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
/**
* Return the namespace.
*/
public String getNamespace() {
return namespace;
}
/**
* Set the namespace.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the pod name.
*/
public String getPodName() {
return podName;
}
/**
* Set the pod name.
*/
public void setPodName(String podName) {
this.podName = podName;
}
/**
* Return the port to use.
*/
public int getPort() {
return port;
}
/**
* Set the port to use.
*/
public void setPort(int port) {
this.port = port;
}
/**
* Return true if clustering is active.
*/
public boolean isClusterActive() {
return clusterActive;
public boolean isActive() {
return active;
}
/**
* Set to true for clustering to be active.
*/
public void setClusterActive(boolean clusterActive) {
this.clusterActive = clusterActive;
public void setActive(boolean active) {
this.active = active;
}
/**
@@ -47,7 +107,14 @@ public class ContainerConfig {
*/
public void loadFromProperties(Properties properties) {
this.properties = properties;
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
this.active = getProperty(properties, "ebean.cluster.active", active);
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
this.podName = properties.getProperty("ebean.cluster.podName", podName);
String portParam = properties.getProperty("ebean.cluster.port");
if (portParam != null) {
this.port = Integer.parseInt(portParam);
}
}
/**
@@ -16,6 +16,8 @@ public class DbMigrationConfig {
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
protected MigrationConfig runnerConfig = new MigrationConfig();
/**
* The database platform to generate migration DDL for.
*/
@@ -105,6 +107,15 @@ public class DbMigrationConfig {
*/
protected String dbPassword;
protected String patchInsertOn;
protected String patchResetChecksumOn;
/**
* Mode used to check non-null columns added via migration have a default value specified etc.
*/
protected boolean strictMode = true;
/**
* Return the DB platform to generate migration DDL for.
* <p>
@@ -352,6 +363,65 @@ public class DbMigrationConfig {
this.dbPassword = dbPassword;
}
/**
* Return migration versions that should be added to history without running.
*/
public String getPatchInsertOn() {
return patchInsertOn;
}
/**
* Set migration versions that should be added to history without running.
* <p>
* Value can be a string containing comma delimited list of version numbers.
* </p>
*/
public void setPatchInsertOn(String patchInsertOn) {
this.patchInsertOn = patchInsertOn;
}
/**
* Return migration versions that should have their checksum reset and not run.
*/
public String getPatchResetChecksumOn() {
return patchResetChecksumOn;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
* Value can be a string containing comma delimited list of version numbers.
* </p>
*/
public void setPatchResetChecksumOn(String patchResetChecksumOn) {
this.patchResetChecksumOn = patchResetChecksumOn;
}
/**
* Return true if strict mode is used which includes a check that non-null columns have a default value.
*/
public boolean isStrictMode() {
String envValue = readEnvironment("ddl.migration.strictMode");
if (!isEmpty(envValue)) {
return Boolean.parseBoolean(envValue.trim());
}
return strictMode;
}
/**
* Set to false to turn off strict mode allowing non-null columns to not have a default value.
*/
public void setStrictMode(boolean strictMode) {
this.strictMode = strictMode;
}
/**
* Return the underlying migration runner configuration allowing for more advanced settings.
*/
public MigrationConfig getRunnerConfig() {
return runnerConfig;
}
/**
* Load the settings from the PropertiesWrapper.
*/
@@ -370,6 +440,8 @@ public class DbMigrationConfig {
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
@@ -446,7 +518,6 @@ public class DbMigrationConfig {
*/
public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) {
MigrationConfig runnerConfig = new MigrationConfig();
runnerConfig.setMetaTable(metaTable);
runnerConfig.setApplySuffix(applySuffix);
runnerConfig.setMigrationPath(migrationPath);
@@ -455,6 +526,12 @@ public class DbMigrationConfig {
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
}
if (patchResetChecksumOn != null) {
runnerConfig.setPatchResetChecksumOn(patchResetChecksumOn);
}
if (properties != null) {
runnerConfig.load(properties);
}
@@ -273,11 +273,29 @@ public class ServerConfig {
*/
private DataSource dataSource;
/**
* The read only data source (can be null).
*/
private DataSource readOnlyDataSource;
/**
* The data source config.
*/
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
/**
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
* I believe this will default to true in some future release (as it has a nice performance benefit).
*
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
*/
private boolean autoReadOnlyDataSource;
/**
* Optional configuration for a read only data source.
*/
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
/**
* The db migration config (migration resource path etc).
*/
@@ -1442,6 +1460,28 @@ public class ServerConfig {
this.dataSource = dataSource;
}
/**
* Return the read only DataSource.
*/
public DataSource getReadOnlyDataSource() {
return readOnlyDataSource;
}
/**
* Set the read only DataSource.
* <p>
* Note that the DataSource is expected to use AutoCommit true mode avoiding the need
* for explicit commit (or rollback).
* </p>
* <p>
* This read only DataSource will be used for implicit query only transactions. It is not
* used if the transaction is created explicitly or if the query is an update or delete query.
* </p>
*/
public void setReadOnlyDataSource(DataSource readOnlyDataSource) {
this.readOnlyDataSource = readOnlyDataSource;
}
/**
* Return the configuration to build a DataSource using Ebean's own DataSource
* implementation.
@@ -1458,6 +1498,42 @@ public class ServerConfig {
this.dataSourceConfig = dataSourceConfig;
}
/**
* Return true if Ebean should create a DataSource for use with implicit read only transactions.
*/
public boolean isAutoReadOnlyDataSource() {
return autoReadOnlyDataSource;
}
/**
* Set to true if Ebean should create a DataSource for use with implicit read only transactions.
*/
public void setAutoReadOnlyDataSource(boolean autoReadOnlyDataSource) {
this.autoReadOnlyDataSource = autoReadOnlyDataSource;
}
/**
* Return the configuration for the read only DataSource.
* <p>
* This is only used if autoReadOnlyDataSource is true.
* </p>
* <p>
* The driver, url, username and password default to the configuration for the main DataSource if they are not
* set on this configuration. This means there is actually no need to set any configuration here and we only
* set configuration for url, username and password etc if it is different from the main DataSource.
* </p>
*/
public DataSourceConfig getReadOnlyDataSourceConfig() {
return readOnlyDataSourceConfig;
}
/**
* Set the configuration for the read only DataSource.
*/
public void setReadOnlyDataSourceConfig(DataSourceConfig readOnlyDataSourceConfig) {
this.readOnlyDataSourceConfig = readOnlyDataSourceConfig;
}
/**
* Return the JNDI name of the DataSource to use.
*/
@@ -2607,6 +2683,7 @@ public class ServerConfig {
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -140,9 +140,25 @@ public class DatabasePlatform {
private static final char BACK_TICK = '`';
/**
* The like clause. Can be overridden to disable default escape character.
* The non-escaped like clause (to stop slash being escaped on some platforms).
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
*/
protected String likeClause = "like ?";
protected String likeClauseRaw = "like ? escape''";
/**
* Escaped like clause for startsWith, endsWith and contains.
*/
protected String likeClauseEscaped = "like ? escape'|'";
/**
* Escape character used for startsWith, endsWith and contains.
*/
protected char likeEscapeChar = '|';
/**
* Characters escaped for startsWith, endsWith and contains.
*/
protected char[] likeSpecialCharacters = { '%', '_', '|' };
protected DbEncrypt dbEncrypt;
@@ -177,8 +193,6 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected char[] specialLikeCharacters = { '%', '_', '\\' };
/**
* Instantiates a new database platform.
*/
@@ -583,8 +597,8 @@ public class DatabasePlatform {
* <p>
* This may include an escape clause to disable a default escape character.
*/
public String getLikeClause() {
return likeClause;
public String getLikeClause(boolean rawLikeExpression) {
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
}
/**
@@ -627,7 +641,7 @@ public class DatabasePlatform {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
boolean escaped = false;
for (char escapeChar: specialLikeCharacters) {
for (char escapeChar: likeSpecialCharacters) {
if (ch == escapeChar) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
@@ -649,6 +663,6 @@ public class DatabasePlatform {
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('\\').append(ch);
sb.append(likeEscapeChar).append(ch);
}
}
@@ -26,10 +26,7 @@ public class DB2Platform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("40001","57033") // key -911/-913
@@ -49,8 +49,8 @@ public class MySqlPlatform extends DatabasePlatform {
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClause = "like binary ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.likeClauseRaw = "like binary ? escape ''";
this.likeClauseEscaped = "like binary ? escape '|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
@@ -10,12 +10,13 @@ import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.RownumSqlLimiter;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Oracle10 and greater specific platform.
* Oracle specific platform.
*/
public class OraclePlatform extends DatabasePlatform {
@@ -29,17 +30,21 @@ public class OraclePlatform extends DatabasePlatform {
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
this.historySupport = new OracleDbHistorySupport();
// Not using getGeneratedKeys as instead we will
// batch load sequences which enables JDBC batch execution
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsGetGeneratedKeys(true);
this.treatEmptyStringsAsNull = true;
this.likeClauseRaw = "like ?";
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("1")
.addDataIntegrity("2291")
.build();
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -81,7 +86,7 @@ public class OraclePlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
@@ -19,12 +19,17 @@ public class SQLitePlatform extends DatabasePlatform {
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
this.booleanDbType = Types.INTEGER;
this.likeClauseRaw = "like ?";
this.likeClauseEscaped = "like ?";
dbTypeMap.put(DbType.BIT, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("integer"));
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("integer"));
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append(ch);
}
}
@@ -39,8 +39,9 @@ public class SqlServerPlatform extends DatabasePlatform {
this.openQuote = "[";
this.closeQuote = "]";
this.specialLikeCharacters = new char[]{'%', '_', '['};
this.likeClause = "like ? COLLATE Latin1_General_BIN";
this.likeSpecialCharacters = new char[]{'%', '_', '['};
this.likeClauseRaw = "like ? collate Latin1_General_BIN";
this.likeClauseEscaped = "like ? collate Latin1_General_BIN";
booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
@@ -83,6 +83,16 @@ public interface DbMigration {
*/
void setPlatform(DatabasePlatform databasePlatform);
/**
* Set to false to turn off strict mode.
* <p>
* Strict mode checks that a column changed to non-null on an existing table via DB migration has a default
* value specified. Set this to false if that isn't the case but it is known that all the existing rows have
* a value specified (there are no existing null values for the column).
* </p>
*/
void setStrictMode(boolean strictMode);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -101,7 +111,6 @@ public interface DbMigration {
* <pre>{@code
*
* DbMigration migration = DbMigration.create();
*
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
@@ -1,17 +0,0 @@
package io.ebean.meta;
import java.util.List;
public interface MetaBeanInfo {
/**
* Collect the current query plan statistics return the non-empty statistics.
*/
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
/**
* Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions).
*/
List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
}
@@ -8,17 +8,12 @@ import java.util.List;
public interface MetaInfoManager {
/**
* Return the MetaBeanInfo for a bean type.
* Collect and return the transaction execution metrics.
*/
MetaBeanInfo getMetaBeanInfo(Class<?> beanClass);
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
/**
* Return all the MetaBeanInfo.
*/
List<MetaBeanInfo> getMetaBeanInfoList();
/**
* Collect and return the query plan statistics for all the beans.
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
* Note that this excludes the query plan statistics where there has been no
* executions (since the last collection with reset).
@@ -1,5 +1,7 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
@@ -14,6 +16,11 @@ public interface MetaQueryPlanStatistic {
*/
Class<?> getBeanType();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
@@ -0,0 +1,43 @@
package io.ebean.meta;
/**
* Timed execution statistics.
*/
public interface MetaTimedMetric {
/**
* Return the metric name.
*/
String getName();
/**
* Return the metric location if defined.
*/
String getLocation();
/**
* Return the time the counters started from.
*/
long getStartTime();
/**
* Return the total count.
*/
long getCount();
/**
* Return the total execution time.
*/
long getTotal();
/**
* Return the max execution time.
*/
long getMax();
/**
* Return the mean execution time.
*/
long getMean();
}
@@ -47,4 +47,9 @@ public interface SpiServer extends EbeanServer {
*/
DataSource getDataSource();
/**
* Return the associated read only DataSource for this EbeanServer instance (can be null).
*/
DataSource getReadOnlyDataSource();
}
@@ -0,0 +1,24 @@
package io.ebean.service;
import io.ebean.ProfileLocation;
/**
* Factory for creating profile locations.
*/
public interface SpiProfileLocationFactory {
/**
* Create a profile location.
*/
ProfileLocation create();
/**
* Create a profile location with a line number.
*/
ProfileLocation create(int lineNumber, String label);
/**
* Create a known location.
*/
ProfileLocation createAt(String location);
}
@@ -1,57 +0,0 @@
package io.ebeaninternal.api;
/**
* A hash for a query plan.
*/
public class HashQueryPlan {
private final String rawSql;
private final int planHash;
private final int bindCount;
public HashQueryPlan(String rawSql, int planHash, int bindCount) {
this.rawSql = rawSql;
this.planHash = planHash;
this.bindCount = bindCount;
}
@Override
public String toString() {
return planHash + ":" + bindCount + (rawSql != null ? ":r" : "");
}
/**
* Return as a partial key. For rawSql hash the sql is part of the key and as such
* needs to be included in order to have a complete key. Typically the MD5 of the sql
* can be used as a shot form proxy for the actual sql.
*/
public String getPartialKey() {
return planHash + "_" + bindCount;
}
@Override
public int hashCode() {
int hc = planHash;
hc = hc * 92821 + bindCount;
hc = hc * 92821 + (rawSql == null ? 0 : rawSql.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof HashQueryPlan)) {
return false;
}
HashQueryPlan e = (HashQueryPlan) obj;
//noinspection StringEquality
return e.planHash == planHash
&& e.bindCount == bindCount
&& ((e.rawSql == rawSql) || (e.rawSql != null && e.rawSql.equals(rawSql)));
}
}
@@ -1,97 +0,0 @@
package io.ebeaninternal.api;
import java.util.Set;
/**
* Used to build HashQueryPlan instances.
*/
public class HashQueryPlanBuilder {
private int planHash;
private int bindCount;
public HashQueryPlanBuilder() {
this.planHash = 92821;
}
@Override
public String toString() {
return planHash + ":" + bindCount;
}
/**
* Add a class to the hash calculation.
*/
public HashQueryPlanBuilder add(Class<?> cls) {
planHash = planHash * 92821 + cls.getName().hashCode();
return this;
}
/**
* Add an object to the hash calculation.
*/
public HashQueryPlanBuilder add(Object object) {
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
return this;
}
/**
* Add the set with order being important.
*/
public HashQueryPlanBuilder addOrdered(Set<?> set) {
if (set == null) {
add(false);
} else {
add(true);
for (Object o : set) {
add(o);
}
}
return this;
}
/**
* Add an integer to the hash calculation.
*/
public HashQueryPlanBuilder add(int hashValue) {
planHash = planHash * 92821 + (hashValue);
return this;
}
/**
* Add a boolean to the hash calculation.
*/
public HashQueryPlanBuilder add(boolean booleanValue) {
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
return this;
}
/**
* Add a number to the bind count for the hash.
*/
public void bind(int extraBindCount) {
bindCount += extraBindCount;
}
public void bindIfNotNull(Object someValue) {
if (someValue != null) {
bindCount++;
}
}
/**
* Build and return the calculated HashQueryPlan.
*/
public String build() {
return planHash + "_" + bindCount;
}
public int getPlanHash() {
return planHash;
}
public int getBindCount() {
return bindCount;
}
}
@@ -157,9 +157,9 @@ public class LoadBeanRequest extends LoadRequest {
for (Object aList : list) {
EntityBean loadedBean = (EntityBean) aList;
loadedIds.add(desc.getId(loadedBean));
if (isLoadCache()) {
desc.cacheBeanPut(loadedBean);
}
}
if (isLoadCache()) {
desc.cacheBeanPutAll(list);
}
if (lazyLoadProperty != null) {
@@ -49,8 +49,10 @@ public class NaturalKeyEntry {
}
private void load(List<NaturalKeyEq> eqList) {
for (NaturalKeyEq eq : eqList) {
map.put(eq.property, eq.value);
if (eqList != null) {
for (NaturalKeyEq eq : eqList) {
map.put(eq.property, eq.value);
}
}
}
@@ -80,6 +80,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void clearQueryStatistics();
/**
* Return the transaction manager.
*/
SpiTransactionManager getTransactionManager();
/**
* Return all the descriptors.
*/
@@ -64,7 +64,7 @@ public interface SpiExpressionRequest {
/**
* Append a DB Like clause.
*/
void appendLike();
void appendLike(boolean rawLikeExpression);
/**
* Escapes a string to use it as exact match in Like clause.
@@ -5,6 +5,7 @@ import io.ebean.EbeanServer;
import io.ebean.ExpressionList;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.bean.CallStack;
import io.ebean.bean.ObjectGraphNode;
@@ -98,17 +99,30 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Delete query.
*/
DELETE(FIND_DELETE),
DELETE(FIND_DELETE, true),
/**
* Update query.
*/
UPDATE(FIND_UPDATE);
UPDATE(FIND_UPDATE, true);
boolean update;
String profileEventId;
Type(String profileEventId) {
this(profileEventId, false);
}
Type(String profileEventId, boolean update) {
this.profileEventId = profileEventId;
this.update = update;
}
/**
* Return true if this is an Update or Delete query (not read only).
*/
public boolean isUpdate() {
return update;
}
public String profileEventId() {
@@ -161,9 +175,19 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
short getProfileId();
/**
* Check for a single "equal to" expression for the Id.
* Return the profile location for this query.
*/
void checkIdEqualTo();
ProfileLocation getProfileLocation();
/**
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
*/
boolean isFindById();
/**
* Return true if this is a "find all" query. Used to set a "find all" profile location if necessary.
*/
boolean isFindAll();
/**
* Return true if AutoTune should be attempted on this query.
@@ -369,7 +393,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Reset AUTO mode to OFF for findList(). Expect explicit cache use with findList().
*/
void resetBeanCacheAutoMode();
void resetBeanCacheAutoMode(boolean findOne);
/**
* Collect natural key data for this query or null if the query does not match
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.Transaction;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
@@ -24,6 +25,11 @@ import java.sql.SQLException;
*/
public interface SpiTransaction extends Transaction {
/**
* Return the user defined label for the transaction.
*/
String getLabel();
/**
* Return the string prefix with the transaction id and label used in logging.
*/
@@ -294,8 +300,23 @@ public interface SpiTransaction extends Transaction {
*/
void profileEvent(SpiProfileTransactionEvent event);
/**
* Set the profileStream to catch and time all the events for this transaction.
*/
void setProfileStream(ProfileStream profileStream);
/**
* Return the stream that profiling events are written to.
*/
ProfileStream profileStream();
/**
* Set the profile location for this transaction.
*/
void setProfileLocation(ProfileLocation profileLocation);
/**
* Return the profile location for this transaction.
*/
ProfileLocation getProfileLocation();
}
@@ -0,0 +1,37 @@
package io.ebeaninternal.api;
import io.ebean.TxScope;
import javax.sql.DataSource;
/**
* Service provider interface for the transaction manager.
*/
public interface SpiTransactionManager {
/**
* Return the main DataSource.
*/
DataSource getDataSource();
/**
* Return the read only DataSource (if defined).
*/
DataSource getReadOnlyDataSource();
/**
* Return the currently active transaction (can be null).
*/
SpiTransaction getActive();
/**
* Push an externally managed transaction into scope (e.g. Spring managed transaction).
*/
ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope);
/**
* Called when an externally managed transaction has completed.
*/
void externalRemoveTransaction();
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
@@ -29,6 +30,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.translate(message, cause);
}
@Override
public void setLabel(String label) {
transaction.setLabel(label);
}
@Override
public String getLabel() {
return transaction.getLabel();
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
@@ -49,11 +60,26 @@ abstract class SpiTransactionProxy implements SpiTransaction {
transaction.profileEvent(event);
}
@Override
public void setProfileStream(ProfileStream profileStream) {
transaction.setProfileStream(profileStream);
}
@Override
public ProfileStream profileStream() {
return transaction.profileStream();
}
@Override
public void setProfileLocation(ProfileLocation profileLocation) {
transaction.setProfileLocation(profileLocation);
}
@Override
public ProfileLocation getProfileLocation() {
return transaction.getProfileLocation();
}
@Override
public void setTenantId(Object tenantId) {
transaction.setTenantId(tenantId);
@@ -2,21 +2,22 @@ package io.ebeaninternal.dbmigration;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.dbmigration.DbMigration;
import io.ebean.annotation.Platform;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.DbMigrationConfig;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.dbmigration.DbMigration;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlWriter;
@@ -27,7 +28,6 @@ import io.ebeaninternal.dbmigration.model.MigrationVersion;
import io.ebeaninternal.dbmigration.model.ModelContainer;
import io.ebeaninternal.dbmigration.model.ModelDiff;
import io.ebeaninternal.dbmigration.model.PlatformDdlWriter;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.DdlScript;
import io.ebeaninternal.extraddl.model.ExtraDdl;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
@@ -53,7 +53,7 @@ import java.util.List;
* </p>
* <pre>{@code
*
* DefaultDbMigration migration = new DefaultDbMigration();
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
*
@@ -88,6 +88,8 @@ public class DefaultDbMigration implements DbMigration {
protected DbConstraintNaming constraintNaming;
protected Boolean strictMode;
/**
* Create for offline migration generation.
*/
@@ -139,6 +141,11 @@ public class DefaultDbMigration implements DbMigration {
}
}
@Override
public void setStrictMode(boolean strictMode) {
this.strictMode = strictMode;
}
/**
* Set the specific platform to generate DDL for.
* <p>
@@ -184,7 +191,7 @@ public class DefaultDbMigration implements DbMigration {
* <h3>Example: Run for a single specific platform</h3>
* <pre>{@code
*
* DefaultDbMigration migration = new DefaultDbMigration();
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
*
@@ -195,7 +202,7 @@ public class DefaultDbMigration implements DbMigration {
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
* <pre>{@code
*
* DefaultDbMigration migration = new DefaultDbMigration();
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
*
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
@@ -486,6 +493,9 @@ public class DefaultDbMigration implements DbMigration {
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
if (strictMode != null && migrationConfig != null) {
migrationConfig.setStrictMode(strictMode);
}
}
/**
@@ -5,6 +5,7 @@ import io.ebean.config.NamingConvention;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.dbplatform.IdType;
import io.ebean.util.StringHelper;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.TableDdl;
@@ -24,7 +25,6 @@ import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebean.util.StringHelper;
import java.io.IOException;
import java.math.BigInteger;
@@ -74,7 +74,7 @@ public class BaseTableDdl implements TableDdl {
*/
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>();
private boolean strict;
private boolean strictMode;
private final boolean sql2011History;
@@ -91,25 +91,24 @@ public class BaseTableDdl implements TableDdl {
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) throws IOException {
DdlMigrationHelp(String tableName, Column column) {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
handleStrictError(tableName, columnName);
}
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
}
/**
* Constructor for DdlMigrationHelp when altering a column.
*/
DdlMigrationHelp(AlterColumn alter) throws IOException {
DdlMigrationHelp(AlterColumn alter) {
this.tableName = alter.getTableName();
this.columnName = alter.getColumnName();
@@ -120,18 +119,16 @@ public class BaseTableDdl implements TableDdl {
// here we add the platform's default update script
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
handleStrictError(tableName, columnName);
}
before = Arrays.asList(platformDdl.getUpdateNullWithDefault());
} else {
before = getScriptsForPlatform(alter.getBefore(), platformDdl.getPlatform().getName());
}
after = getScriptsForPlatform(alter.getAfter(), platformDdl.getPlatform().getName());
}
public void writeBefore(DdlBuffer buffer) throws IOException {
void writeBefore(DdlBuffer buffer) throws IOException {
if (!before.isEmpty()) {
buffer.end();
}
@@ -141,8 +138,8 @@ public class BaseTableDdl implements TableDdl {
}
}
public void writeAfter(DdlBuffer buffer) throws IOException {
// here we run postmigration scripts
void writeAfter(DdlBuffer buffer) throws IOException {
// here we run post migration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
buffer.endOfStatement();
@@ -175,11 +172,10 @@ public class BaseTableDdl implements TableDdl {
return StringHelper.replaceString(ret, "${default}", defaultValue);
}
private void handleStrictError(String message) {
if (strict) {
private void handleStrictError(String tableName, String columnName) {
if (strictMode) {
String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or specify dbMigration.setStrictMode(false)";
throw new IllegalArgumentException(message);
} else {
System.err.println("Error in DDL: " + message);
}
}
@@ -198,7 +194,7 @@ public class BaseTableDdl implements TableDdl {
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
this.platformDdl = platformDdl;
this.platformDdl.configure(serverConfig);
this.strict = true; // TODO RPr serverConfig.getMigrationConfig().isStrict();
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
this.sql2011History = hist != null && hist.isStandardsBased();
}
@@ -19,6 +19,7 @@ public class Oracle10Ddl extends PlatformDdl {
this.columnSetNotnull = "not null";
this.columnSetNull = "null";
this.columnSetDefault = "default";
this.identitySuffix = " generated always as identity";
}
}
@@ -11,6 +11,7 @@ import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.IndexDefinition;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
import io.ebeaninternal.server.deploy.id.ImportedId;
@@ -148,8 +149,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
TableJoinColumn[] columns = p.getTableJoin().columns();
if (columns.length == 0) {
String msg = "No join columns for " + p.getFullBeanName();
throw new RuntimeException(msg);
throw new RuntimeException("No join columns for " + p.getFullBeanName());
}
ImportedId importedId = p.getImportedId();
@@ -231,6 +231,12 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
if (p.getBeanDescriptor().isUseIdGenerator()) {
col.setIdentity(true);
}
TableJoin primaryKeyJoin = p.getBeanDescriptor().getPrimaryKeyJoin();
if (primaryKeyJoin != null) {
TableJoinColumn[] columns = primaryKeyJoin.columns();
col.setReferences(primaryKeyJoin.getTable() + "." + columns[0].getForeignDbColumn());
col.setForeignKeyName(determineForeignKeyConstraintName(col.getName()));
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
col.setDbMigrationInfos(p.getDbMigrationInfos());
@@ -5,6 +5,7 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
/**
* Modify aware wrapper of a list.
@@ -32,6 +33,19 @@ public class ModifyAwareList<E> implements List<E>, ModifyAwareOwner {
return list.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ModifyAwareList)) return false;
ModifyAwareList<?> that = (ModifyAwareList<?>) o;
return Objects.equals(list, that.list);
}
@Override
public int hashCode() {
return Objects.hash(list);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,6 +3,7 @@ package io.ebeaninternal.json;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
@@ -19,14 +20,14 @@ public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
*/
final Map<K, V> map;
public ModifyAwareMap(Map<K, V> underyling) {
this.map = underyling;
public ModifyAwareMap(Map<K, V> underlying) {
this.map = underlying;
this.owner = new ModifyAwareFlag();
}
public ModifyAwareMap(ModifyAwareOwner owner, Map<K, V> underyling) {
public ModifyAwareMap(ModifyAwareOwner owner, Map<K, V> underlying) {
this.owner = owner;
this.map = underyling;
this.map = underlying;
}
@Override
@@ -34,6 +35,19 @@ public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
return map.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ModifyAwareMap)) return false;
ModifyAwareMap<?, ?> that = (ModifyAwareMap<?, ?>) o;
return Objects.equals(map, that.map);
}
@Override
public int hashCode() {
return Objects.hash(map);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -2,6 +2,7 @@ package io.ebeaninternal.json;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
/**
@@ -51,6 +52,19 @@ public class ModifyAwareSet<E> implements Set<E>, ModifyAwareOwner {
return set.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ModifyAwareSet)) return false;
ModifyAwareSet<?> that = (ModifyAwareSet<?>) o;
return Objects.equals(set, that.set);
}
@Override
public int hashCode() {
return Objects.hash(set);
}
@Override
public boolean add(E o) {
if (set.add(o)) {
@@ -0,0 +1,25 @@
package io.ebeaninternal.metric;
/**
* Factory to create timed metric counters.
*/
public interface MetricFactory {
/**
* Return the factory instance.
*/
static MetricFactory get() {
return MetricServiceProvider.get();
}
/**
* Create a timed metric group.
*/
TimedMetricMap createTimedMetricMap(String name);
/**
* Create a Timed metric.
*/
TimedMetric createTimedMetric(String name);
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.metric;
import io.ebeaninternal.server.profile.DMetricFactory;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Lookup MetricFactory service.
*/
class MetricServiceProvider {
private static MetricFactory metricFactory = init();
private static MetricFactory init() {
Iterator<MetricFactory> loader = ServiceLoader.load(MetricFactory.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
return new DMetricFactory();
}
/**
* Return the MetricFactory implementation.
*/
static MetricFactory get() {
return metricFactory;
}
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
/**
* Metric for timed events like transaction execution times.
*/
public interface TimedMetric {
/**
* Add a time event (usually in microseconds).
*/
void add(long value);
/**
* Return true if there are no metrics collected since the last collection.
*/
boolean isEmpty();
/**
* Collect the timed metric statistics.
*/
TimedMetricStats collect(boolean reset);
/**
* Add non empty metrics to the result.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
/**
* A map of timed metrics keyed by a string.
*/
public interface TimedMetricMap {
/**
* Add an execution for the given key.
*/
void add(String key, long exeMicros);
/**
* Add non empty metrics to the given result.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
}
@@ -0,0 +1,14 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
/**
* Extend public MetaTimedMetric with ability to set details from profile location.
*/
public interface TimedMetricStats extends MetaTimedMetric {
/**
* Additionally set the location.
*/
void setLocation(String location);
}
@@ -122,8 +122,7 @@ public class CacheChangeSet {
*/
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
ManyKey key = new ManyKey(desc, manyProperty);
ManyChange manyChange = manyChangeMap.computeIfAbsent(key, ManyChange::new);
return manyChange;
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
}
/**
@@ -0,0 +1,85 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.cluster.ClusterManager;
/**
* Configuration options when creating the default cache manager.
*/
public class CacheManagerOptions {
private final ClusterManager clusterManager;
private final ServerConfig serverConfig;
private final boolean localL2Caching;
private CurrentTenantProvider currentTenantProvider;
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
private ServerCacheOptions beanDefault = new ServerCacheOptions();
private ServerCacheOptions queryDefault = new ServerCacheOptions();
CacheManagerOptions() {
this.localL2Caching = true;
this.clusterManager = null;
this.serverConfig = null;
this.cacheFactory = new DefaultServerCacheFactory();
this.beanDefault = new ServerCacheOptions();
this.queryDefault = new ServerCacheOptions();
}
public CacheManagerOptions(ClusterManager clusterManager, ServerConfig serverConfig, boolean localL2Caching) {
this.clusterManager = clusterManager;
this.serverConfig = serverConfig;
this.localL2Caching = localL2Caching;
this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
}
public CacheManagerOptions with(ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
this.beanDefault = beanDefault;
this.queryDefault = queryDefault;
return this;
}
public CacheManagerOptions with(ServerCacheFactory cacheFactory) {
this.cacheFactory = cacheFactory;
return this;
}
public CacheManagerOptions with(CurrentTenantProvider currentTenantProvider) {
this.currentTenantProvider = currentTenantProvider;
return this;
}
public String getServerName() {
return (serverConfig == null) ? "db" : serverConfig.getName();
}
public boolean isLocalL2Caching() {
return localL2Caching;
}
public ServerCacheFactory getCacheFactory() {
return cacheFactory;
}
public ServerCacheOptions getBeanDefault() {
return beanDefault;
}
public ServerCacheOptions getQueryDefault() {
return queryDefault;
}
public CurrentTenantProvider getCurrentTenantProvider() {
return currentTenantProvider;
}
public ClusterManager getClusterManager() {
return clusterManager;
}
}
@@ -44,12 +44,21 @@ public class DefaultCacheAdapter implements ServerCacheManager {
@Override
public void clear(Class<?> beanType) {
cacheManager.getBeanCache(beanType).clear();
cacheManager.getQueryCache(beanType).clear();
cacheManager.clear(beanType);
}
@Override
public void clearAll() {
cacheManager.clearAll();
}
@Override
public void clearAllLocal() {
cacheManager.clearAllLocal();
}
@Override
public void clearLocal(Class<?> beanType) {
cacheManager.clearLocal(beanType);
}
}
@@ -8,16 +8,24 @@ import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.util.AnnotationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
/**
* Manages the construction of caches.
*/
class DefaultCacheHolder {
private static final Logger log = LoggerFactory.getLogger("io.ebean.cache.ALL");
private final ConcurrentHashMap<String, ServerCache> allCaches = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Set<String>> collectIdCaches = new ConcurrentHashMap<>();
private final ServerCacheFactory cacheFactory;
private final ServerCacheOptions beanDefault;
@@ -25,6 +33,10 @@ class DefaultCacheHolder {
private final CurrentTenantProvider tenantProvider;
DefaultCacheHolder(CacheManagerOptions builder) {
this(builder.getCacheFactory(), builder.getBeanDefault(), builder.getQueryDefault(), builder.getCurrentTenantProvider());
}
/**
* Create with a cache factory and default cache options.
*
@@ -42,7 +54,6 @@ class DefaultCacheHolder {
ServerCache getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
return getCacheInternal(beanType, cacheKey, type);
}
private String key(String cacheKey, ServerCacheType type) {
@@ -60,16 +71,43 @@ class DefaultCacheHolder {
private ServerCache createCache(Class<?> beanType, ServerCacheType type, String key) {
ServerCacheOptions options = getCacheOptions(beanType, type);
if (type == ServerCacheType.COLLECTION_IDS) {
synchronized (this) {
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
}
}
return cacheFactory.createCache(type, key, tenantProvider, options);
}
void clearAll() {
log.debug("clearAll");
for (ServerCache serverCache : allCaches.values()) {
serverCache.clear();
}
}
public void clear(String name) {
log.debug("clear {}", name);
clearIfExists(key(name, ServerCacheType.QUERY));
clearIfExists(key(name, ServerCacheType.BEAN));
clearIfExists(key(name, ServerCacheType.NATURAL_KEY));
Set<String> keys = collectIdCaches.get(name);
if (keys != null) {
for (String collectionIdKey : keys) {
clearIfExists(collectionIdKey);
}
}
}
private void clearIfExists(String fullKey) {
ServerCache cache = allCaches.get(fullKey);
if (cache != null) {
log.trace("clear cache {}", fullKey);
cache.clear();
}
}
/**
* Return the cache options for a given bean type.
*/
@@ -6,15 +6,14 @@ import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheStatistics;
import io.ebean.cache.TenantAwareKey;
import io.ebean.config.CurrentTenantProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.List;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -217,20 +216,23 @@ public class DefaultServerCache implements ServerCache {
}
}
@Override
public void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
}
/**
* Put a value into the cache.
*/
@Override
public Object put(Object id, Object value) {
public void put(Object id, Object value) {
Object key = key(id);
CacheEntry entry = map.put(key, new CacheEntry(key, value));
if (entry == null) {
insertCount.increment();
return null;
} else {
updateCount.increment();
return entry.getValue();
}
}
@@ -238,14 +240,11 @@ public class DefaultServerCache implements ServerCache {
* Remove an entry from the cache.
*/
@Override
public Object remove(Object id) {
public void remove(Object id) {
CacheEntry entry = map.remove(key(id));
if (entry == null) {
return null;
} else {
if (entry != null) {
removeCount.increment();
return entry.getValue();
}
}
@@ -359,10 +358,8 @@ public class DefaultServerCache implements ServerCache {
private static final long serialVersionUID = 1L;
@Override
public int compare(CacheEntry entry1, CacheEntry entry2) {
long x = entry1.getLastAccessTime();
long y = entry2.getLastAccessTime();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
public int compare(CacheEntry e1, CacheEntry e2) {
return Long.compare(e1.getLastAccessTime(), e2.getLastAccessTime());
}
}
@@ -1,34 +1,37 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebeaninternal.server.cluster.ClusterManager;
/**
* Manages the bean and query caches.
*/
public class DefaultServerCacheManager implements SpiCacheManager {
private final ClusterManager clusterManager;
private final DefaultCacheHolder cacheHolder;
private final boolean localL2Caching;
private final String serverName;
/**
* Create with a cache factory and default cache options.
*/
public DefaultServerCacheManager(boolean localL2Caching, CurrentTenantProvider tenantProvider, ServerCacheFactory cacheFactory,
ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
this.localL2Caching = localL2Caching;
this.cacheHolder = new DefaultCacheHolder(cacheFactory, beanDefault, queryDefault, tenantProvider);
public DefaultServerCacheManager(CacheManagerOptions builder) {
this.clusterManager = builder.getClusterManager();
this.serverName = builder.getServerName();
this.localL2Caching = builder.isLocalL2Caching();
this.cacheHolder = new DefaultCacheHolder(builder);
}
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheManager() {
this(true, null, new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
this(new CacheManagerOptions());
}
@Override
@@ -42,6 +45,27 @@ public class DefaultServerCacheManager implements SpiCacheManager {
@Override
public void clearAll() {
cacheHolder.clearAll();
if (clusterManager != null) {
clusterManager.cacheClearAll(serverName);
}
}
@Override
public void clearAllLocal() {
cacheHolder.clearAll();
}
@Override
public void clear(Class<?> beanType) {
cacheHolder.clear(name(beanType));
if (clusterManager != null) {
clusterManager.cacheClear(serverName, beanType);
}
}
@Override
public void clearLocal(Class<?> beanType) {
cacheHolder.clear(name(beanType));
}
@Override
@@ -0,0 +1,93 @@
package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Cache events broadcast across the cluster.
*/
public class RemoteCacheEvent {
private boolean clearAll;
private List<String> clearCaches;
/**
* Clear all the caches.
*/
public RemoteCacheEvent(boolean clearAll) {
this.clearAll = clearAll;
this.clearCaches = null;
}
/**
* Clear caches for the given bean type.
*/
public RemoteCacheEvent(Class<?> beanType) {
this.clearAll = false;
this.clearCaches = new ArrayList<>(1);
this.clearCaches.add(beanType.getName());
}
/**
* Create with the read options.
*/
public RemoteCacheEvent(boolean clearAll, List<String> beanTypes) {
this.clearAll = clearAll;
this.clearCaches = beanTypes;
}
public boolean isClearAll() {
return clearAll;
}
public List<String> getClearCaches() {
return clearCaches;
}
public String toString() {
return "clearAll:" + clearAll + " caches:" + clearCaches;
}
public static RemoteCacheEvent readBinaryMessage(DataInput dataInput) throws IOException {
boolean clearAll = dataInput.readBoolean();
int size = dataInput.readInt();
List<String> clearCache = null;
if (size > 0) {
clearCache = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
clearCache.add(dataInput.readUTF());
}
}
return new RemoteCacheEvent(clearAll, clearCache);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
int bufferSize = (clearCaches == null) ? 0 : clearCaches.size() * 30;
BinaryMessage msg = new BinaryMessage(bufferSize + 10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_CACHE);
os.writeBoolean(clearAll);
if (clearCaches == null) {
os.writeInt(0);
} else {
os.writeInt(clearCaches.size());
for (String cacheName : clearCaches) {
os.writeUTF(cacheName);
}
}
msgList.add(msg);
}
}
@@ -36,9 +36,24 @@ public interface SpiCacheManager {
*/
ServerCache getQueryCache(Class<?> beanType);
/**
* Clear the caches for the given bean type.
*/
void clear(Class<?> beanType);
/**
* Clear all the caches.
*/
void clearAll();
/**
* Clear all local caches.
*/
void clearAllLocal();
/**
* Clear local caches for the given bean type.
*/
void clearLocal(Class<?> beanType);
}
@@ -23,6 +23,7 @@ public class BinaryMessage {
public static final int TYPE_MSGCONTROL = 0;
public static final int TYPE_BEANIUD = 1;
public static final int TYPE_TABLEIUD = 2;
public static final int TYPE_CACHE = 3;
public static final int TYPE_MSGACK = 8;
public static final int TYPE_MSGRESEND = 9;
@@ -5,12 +5,10 @@ import java.util.List;
/**
* Holds a List of BinaryMessage's.
*
* @author rbygrave
*/
public class BinaryMessageList {
final ArrayList<BinaryMessage> list = new ArrayList<>();
final List<BinaryMessage> list = new ArrayList<>();
public void add(BinaryMessage msg) {
list.add(msg);
@@ -24,5 +24,4 @@ public interface ClusterBroadcast {
* Send a transaction event to all the members of the cluster.
*/
void broadcast(RemoteTransactionEvent remoteTransEvent);
}
@@ -1,6 +1,6 @@
package io.ebeaninternal.server.cluster;
import java.util.Properties;
import io.ebean.config.ContainerConfig;
/**
* Factory to create the cluster broadcast service.
@@ -10,5 +10,5 @@ public interface ClusterBroadcastFactory {
/**
* Create the cluster transport with the manager and deployment properties.
*/
ClusterBroadcast create(ClusterManager manager, Properties properties);
ClusterBroadcast create(ClusterManager manager, ContainerConfig config);
}
@@ -17,8 +17,6 @@ public class ClusterManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<>();
private final Object monitor = new Object();
@@ -27,12 +25,14 @@ public class ClusterManager {
private boolean started;
private boolean shutdown;
public ClusterManager(ContainerConfig config) {
if (!config.isClusterActive()) {
broadcast = null;
ClusterBroadcastFactory factory = createFactory();
if (factory != null) {
broadcast = factory.create(this, config);
} else {
ClusterBroadcastFactory factory = createFactory();
broadcast = factory.create(this, config.getProperties());
broadcast = null;
}
}
@@ -47,10 +47,6 @@ public class ClusterManager {
if (iterator.hasNext()) {
factory = iterator.next();
}
if (factory == null) {
throw new IllegalStateException("No ClusterTransportFactory found in classpath. "
+ " Probably need to add the avaje-ebeanorm-cluster dependency");
}
return factory;
}
@@ -76,6 +72,24 @@ public class ClusterManager {
}
}
/**
* Broadcast a cache clear all event to the cluster.
*/
public void cacheClearAll(String serverName) {
if (broadcast != null) {
broadcast.broadcast(new RemoteTransactionEvent(serverName).cacheClearAll());
}
}
/**
* Broadcast a cache clear event to the cluster.
*/
public void cacheClear(String serverName, Class<?> beanType) {
if (broadcast != null) {
broadcast.broadcast(new RemoteTransactionEvent(serverName).cacheClear(beanType));
}
}
/**
* Return true if clustering is on.
*/
@@ -99,8 +113,8 @@ public class ClusterManager {
* Shutdown the service and Deregister from the cluster.
*/
public void shutdown() {
if (broadcast != null) {
logger.info("ClusterManager shutdown ");
if (broadcast != null && !shutdown) {
shutdown = true;
broadcast.shutdown();
}
}
@@ -21,10 +21,14 @@ public class CObjectGraphNodeStatistics {
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
public CObjectGraphNodeStatistics(ObjectGraphNode node) {
CObjectGraphNodeStatistics(ObjectGraphNode node) {
this.node = node;
}
public boolean isEmpty() {
return count.sum() == 0;
}
public void add(long beanCount, long exeMicros) {
count.increment();
totalTime.add(exeMicros);
@@ -10,5 +10,5 @@ public interface CallStackFactory {
/**
* Create and return the CallStack given the stack trace elements.
*/
CallStack createCallStack(StackTraceElement[] finalTrace);
CallStack createCallStack();
}
@@ -2,13 +2,56 @@ package io.ebeaninternal.server.core;
import io.ebean.bean.CallStack;
import java.util.Arrays;
/**
* Default CallStackFactory where the Hash function for StackTraceElement includes the line number.
*/
public class DefaultCallStackFactory implements CallStackFactory {
private static final int IGNORE_LEADING_ELEMENTS = 5;
private static final String IO_EBEAN = "io.ebean";
private final int maxCallStack;
DefaultCallStackFactory(int maxCallStack) {
this.maxCallStack = maxCallStack;
}
@Override
public CallStack createCallStack(StackTraceElement[] finalTrace) {
public CallStack createCallStack() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// ignore the first 6 as they are always avaje stack elements
int startIndex = IGNORE_LEADING_ELEMENTS;
// find the first non-avaje stackElement
for (; startIndex < stackTrace.length; startIndex++) {
if (!stackTrace[startIndex].getClassName().startsWith(IO_EBEAN)) {
break;
}
}
int stackLength = stackTrace.length - startIndex;
if (stackLength > maxCallStack) {
// maximum of maxCallStack stackTrace elements
stackLength = maxCallStack;
}
// create the 'interesting' part of the stackTrace
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
if (stackLength < 1) {
// this should not really happen
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
}
return createCallStack(finalTrace);
}
private CallStack createCallStack(StackTraceElement[] finalTrace) {
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
}
@@ -4,7 +4,6 @@ import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.service.SpiContainer;
import io.ebean.config.ContainerConfig;
import io.ebean.config.PropertyMap;
import io.ebean.config.ServerConfig;
@@ -12,10 +11,12 @@ import io.ebean.config.TenantMode;
import io.ebean.config.UnderscoreNamingConvention;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebean.service.SpiContainer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiContainerBootup;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.cache.CacheManagerOptions;
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
import io.ebeaninternal.server.cache.SpiCacheManager;
@@ -71,6 +72,7 @@ public class DefaultContainer implements SpiContainer {
@Override
public void shutdown() {
clusterManager.shutdown();
ShutdownManager.shutdown();
}
@@ -201,7 +203,11 @@ public class DefaultContainer implements SpiContainer {
}
ServerCacheFactory factory = plugin.create(serverConfig, executor);
return new DefaultServerCacheManager(localL2Caching, serverConfig.getCurrentTenantProvider(), factory, beanOptions, queryOptions);
CacheManagerOptions builder = new CacheManagerOptions(clusterManager, serverConfig, localL2Caching)
.with(beanOptions, queryOptions)
.with(factory);
return new DefaultServerCacheManager(builder);
}
/**
@@ -272,18 +278,21 @@ public class DefaultContainer implements SpiContainer {
*/
private void setDataSource(ServerConfig config) {
if (config.getDataSource() == null) {
config.setDataSource(getDataSourceFromConfig(config));
config.setDataSource(getDataSourceFromConfig(config, false));
}
if (config.getReadOnlyDataSource() == null && config.isAutoReadOnlyDataSource()) {
config.setReadOnlyDataSource(getDataSourceFromConfig(config, true));
}
}
private DataSource getDataSourceFromConfig(ServerConfig config) {
private DataSource getDataSourceFromConfig(ServerConfig config, boolean readOnly) {
if (isOfflineMode(config)) {
logger.debug("... DbOffline using platform [{}]", DbOffline.getPlatform());
return null;
}
if (config.getDataSourceJndiName() != null) {
if (!readOnly && config.getDataSourceJndiName() != null) {
DataSource ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName());
if (ds == null) {
throw new PersistenceException("JNDI lookup for DataSource " + config.getDataSourceJndiName() + " returned null.");
@@ -292,7 +301,7 @@ public class DefaultContainer implements SpiContainer {
}
}
DataSourceConfig dsConfig = config.getDataSourceConfig();
DataSourceConfig dsConfig = (readOnly) ? config.getReadOnlyDataSourceConfig() : config.getDataSourceConfig();
if (dsConfig == null) {
throw new PersistenceException("No DataSourceConfig defined for " + config.getName());
}
@@ -317,7 +326,15 @@ public class DefaultContainer implements SpiContainer {
attachListener(config, dsConfig);
return factory.createPool(config.getName(), dsConfig);
if (readOnly) {
// setup to use AutoCommit such that we skip explicit commit
dsConfig.setAutoCommit(true);
//dsConfig.setReadOnly(true);
dsConfig.setDefaults(config.getDataSourceConfig());
dsConfig.setIsolationLevel(config.getDataSourceConfig().getIsolationLevel());
}
String poolName = config.getName() + (readOnly ? "-ro" : "");
return factory.createPool(poolName, dsConfig);
}
/**
@@ -1,9 +1,11 @@
package io.ebeaninternal.server.core;
import io.ebean.meta.MetaBeanInfo;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import java.util.ArrayList;
import java.util.List;
@@ -15,41 +17,32 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
private final DefaultServer server;
public DefaultMetaInfoManager(DefaultServer server) {
DefaultMetaInfoManager(DefaultServer server) {
this.server = server;
}
@Override
public MetaBeanInfo getMetaBeanInfo(Class<?> beanClass) {
return server.getBeanDescriptor(beanClass);
}
@Override
public List<MetaBeanInfo> getMetaBeanInfoList() {
return new ArrayList<>(server.getBeanDescriptors());
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return server.collectTransactionStatistics(reset);
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
List<MetaQueryPlanStatistic> list = new ArrayList<>();
for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) {
list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset));
CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset);
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
desc.collectQueryPlanStatistics(collector);
}
return list;
return collector.getList();
}
@Override
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
List<MetaObjectGraphNodeStats> list = new ArrayList<>();
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
MetaObjectGraphNodeStats nodeStats = nodeStatistics.get(reset);
if (nodeStats.getCount() > 0) {
// Only collection non-empty statistics
list.add(nodeStats);
if (!nodeStatistics.isEmpty()) {
list.add(nodeStatistics.get(reset));
}
}
return list;
@@ -12,6 +12,7 @@ import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.SqlQuery;
@@ -25,7 +26,6 @@ import io.ebean.UpdateQuery;
import io.ebean.ValuePair;
import io.ebean.Version;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallStack;
import io.ebean.bean.EntityBean;
@@ -46,6 +46,7 @@ import io.ebean.event.BeanPersistController;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
@@ -53,7 +54,6 @@ import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import io.ebeaninternal.api.LoadBeanRequest;
import io.ebeaninternal.api.LoadManyRequest;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -61,10 +61,12 @@ import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.dbmigration.DdlGenerator;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.cache.RemoteCacheEvent;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -91,7 +93,6 @@ import io.ebeaninternal.server.text.csv.TCsvReader;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import io.ebeanservice.docstore.api.DocStoreIntegration;
@@ -102,7 +103,6 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
@@ -122,10 +122,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private static final Logger logger = LoggerFactory.getLogger(DefaultServer.class);
private static final int IGNORE_LEADING_ELEMENTS = 5;
private static final String IO_EBEAN = "io.ebean";
private final ServerConfig serverConfig;
private final String serverName;
@@ -134,19 +130,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final TransactionManager transactionManager;
private final TransactionScopeManager transactionScopeManager;
private final DataTimeZone dataTimeZone;
private final CallStackFactory callStackFactory = new DefaultCallStackFactory();
private final int maxCallStack;
/**
* Ebean defaults this to true but for EJB compatible behaviour set this to
* false;
*/
private final boolean rollbackOnChecked;
private final CallStackFactory callStackFactory;
/**
* Handles the save, delete, updateSql CallableSql.
@@ -253,9 +239,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.updateAllPropertiesInBatch = serverConfig.isUpdateAllPropertiesInBatch();
this.collectQueryOrigins = serverConfig.isCollectQueryOrigins();
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
this.maxCallStack = serverConfig.getMaxCallStack();
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
this.callStackFactory = initCallStackFactory(serverConfig);
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
@@ -271,7 +255,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
this.transactionScopeManager = config.createTransactionScopeManager(transactionManager);
this.documentStore = docStoreComponents.documentStore();
this.serverPlugins = config.getPlugins();
@@ -283,6 +266,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
ShutdownManager.registerEbeanServer(this);
}
/**
* Create the CallStackFactory depending if AutoTune is being used.
*/
private CallStackFactory initCallStackFactory(ServerConfig serverConfig) {
if (!serverConfig.getAutoTuneConfig().isActive()) {
// use a common CallStack for performance as we don't care with no AutoTune
return new NoopCallStackFactory();
}
return new DefaultCallStackFactory(serverConfig.getMaxCallStack());
}
private void configureServerPlugins() {
autoTuneService.startup();
@@ -379,6 +373,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return transactionManager.getDataSource();
}
@Override
public DataSource getReadOnlyDataSource() {
return transactionManager.getReadOnlyDataSource();
}
@Override
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
@@ -553,12 +552,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void externalModification(TransactionEventTable tableEvent) {
SpiTransaction t = transactionScopeManager.get();
if (t != null) {
t.getEvent().add(tableEvent);
} else {
transactionManager.externalModification(tableEvent);
}
transactionManager.externalModification(tableEvent);
}
/**
@@ -619,7 +613,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
id = desc.convertId(id);
PersistenceContext pc = null;
SpiTransaction t = transactionScopeManager.get();
SpiTransaction t = transactionManager.getActive();
if (t != null) {
pc = t.getPersistenceContext();
Object existing = desc.contextGet(pc, id);
@@ -658,8 +652,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public Transaction createTransaction() {
return transactionManager.createTransaction(0, true, -1);
return transactionManager.createTransaction(true, -1);
}
/**
@@ -670,8 +663,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public Transaction createTransaction(TxIsolation isolation) {
return transactionManager.createTransaction(0, true, isolation.getLevel());
return transactionManager.createTransaction(true, isolation.getLevel());
}
@Override
@@ -681,7 +673,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> T executeCall(TxScope scope, Callable<T> c) {
ScopedTransaction scopeTrans = scopedTransaction(scope);
ScopedTransaction scopeTrans = transactionManager.beginScopedTransaction(scope);
try {
return c.call();
@@ -703,7 +695,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void execute(TxScope scope, Runnable r) {
ScopedTransaction t = scopedTransaction(scope);
ScopedTransaction t = transactionManager.beginScopedTransaction(scope);
try {
r.run();
@@ -718,43 +710,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
/**
* Determine whether to create a new transaction or not.
* <p>
* This will also potentially throw exceptions for MANDATORY and NEVER types.
* </p>
*/
private boolean createNewTransaction(SpiTransaction current, TxType type) {
switch (type) {
case REQUIRED:
return current == null;
case REQUIRES_NEW:
return true;
case MANDATORY:
if (current == null) {
throw new PersistenceException("Transaction missing when MANDATORY");
}
return false;
case SUPPORTS:
return current == null;
case NEVER:
if (current != null) {
throw new PersistenceException("Transaction exists for Transactional NEVER");
}
return true; // always use NoTransaction instance
case NOT_SUPPORTED:
return true; // always use NoTransaction instance
default:
throw new RuntimeException("Should never get here?");
}
}
@Override
public void scopedTransactionEnter(TxScope txScope) {
beginTransaction(txScope);
@@ -762,12 +717,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void scopedTransactionExit(Object returnOrThrowable, int opCode) {
ScopedTransaction st = (ScopedTransaction) transactionScopeManager.getScoped();
if (st != null) {
// can be null for Supports as that can start as a 'No Transaction' and then
// effectively be replaced by transactions inside the scope
st.complete(returnOrThrowable, opCode);
}
transactionManager.exitScopedTransaction(returnOrThrowable, opCode);
}
/**
@@ -775,7 +725,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public SpiTransaction currentServerTransaction() {
return transactionScopeManager.get();
return transactionManager.getActive();
}
/**
@@ -794,54 +744,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction beginTransaction(TxScope txScope) {
return scopedTransaction(txScope);
}
/**
* Create a Scoped transaction which internally can 'nest' transactions on it's own stack.
*/
ScopedTransaction scopedTransaction(TxScope txScope) {
txScope = initTxScope(txScope);
boolean setToScope = false;
ScopedTransaction txnContainer = (ScopedTransaction) transactionScopeManager.get();
if (txnContainer == null) {
setToScope = true;
txnContainer = new ScopedTransaction(transactionScopeManager);
}
SpiTransaction transaction = txnContainer.current();
TxType type = txScope.getType();
boolean createTransaction = createNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel());
}
}
txnContainer.push(new ScopeTrans(rollbackOnChecked, createTransaction, transaction, txScope));
if (setToScope) {
transactionScopeManager.set(txnContainer);
}
return txnContainer;
}
private TxScope initTxScope(TxScope txScope) {
if (txScope == null) {
return new TxScope();
} else {
// check for implied batch mode via setting batchSize
txScope.checkBatchMode();
return txScope;
}
return transactionManager.beginScopedTransaction(txScope);
}
/**
@@ -853,9 +756,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction beginTransaction(TxIsolation isolation) {
// start an explicit transaction
SpiTransaction t = transactionManager.createTransaction(0, true, isolation.getLevel());
SpiTransaction t = transactionManager.createTransaction(true, isolation.getLevel());
try {
transactionScopeManager.set(t);
// note that we are not supporting nested scoped transactions in this case
transactionManager.set(t);
} catch (PersistenceException existingTransactionError) {
t.end();
throw existingTransactionError;
@@ -869,7 +773,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public Transaction currentTransaction() {
return transactionScopeManager.get();
return transactionManager.getActive();
}
@Override
@@ -882,7 +786,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void commitTransaction() {
transactionScopeManager.commit();
transactionManager.scope().commit();
}
/**
@@ -890,7 +794,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void rollbackTransaction() {
transactionScopeManager.rollback();
transactionManager.scope().rollback();
}
/**
@@ -923,7 +827,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void endTransaction() {
transactionScopeManager.end();
transactionManager.scope().end();
}
/**
@@ -1104,6 +1008,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
if (t == null) {
t = currentServerTransaction();
}
query.setDefaultRawSqlIfRequired();
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
@@ -1113,6 +1020,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
query.selectAllForLazyLoadProperty();
ProfileLocation profileLocation = query.getProfileLocation();
if (profileLocation != null) {
profileLocation.obtain();
}
// if determine cost and no origin for AutoTune
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
@@ -1193,6 +1104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
request.profileLocationById();
if (request.isUseDocStore()) {
return docStore().find(request);
}
@@ -1214,9 +1126,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public <T> T findOne(Query<T> query, Transaction transaction) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.checkIdEqualTo();
Object id = spiQuery.getId();
if (id != null) {
if (spiQuery.isFindById()) {
// actually a find by Id query
return findId(query, transaction);
}
@@ -1517,12 +1427,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
if (!findOne) {
request.resetBeanCacheAutoMode();
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
request.profileLocationAll();
request.resetBeanCacheAutoMode(findOne);
Object result = request.getFromQueryCache();
if (result != null) {
return (List<T>) result;
}
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
return request.getBeanCacheHits();
@@ -1577,7 +1486,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public List<SqlRow> findList(SqlQuery query, Transaction t) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
try {
request.initTransIfRequired();
return request.findList();
@@ -1965,6 +1873,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return beanDescriptorManager.getBeanDescriptorList();
}
/**
* Return the transaction manager.
*/
@Override
public SpiTransactionManager getTransactionManager() {
return transactionManager;
}
public void register(BeanPersistController c) {
List<BeanDescriptor<?>> list = beanDescriptorManager.getBeanDescriptorList();
for (BeanDescriptor<?> aList : list) {
@@ -2072,6 +1988,30 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void remoteTransactionEvent(RemoteTransactionEvent event) {
transactionManager.remoteTransactionEvent(event);
processRemoteCacheEvent(event);
}
/**
* Process a cache event coming from another server in the cluster.
*/
private void processRemoteCacheEvent(RemoteTransactionEvent event) {
RemoteCacheEvent cacheEvent = event.getRemoteCacheEvent();
if (cacheEvent != null) {
if (cacheEvent.isClearAll()) {
serverCacheManager.clearAllLocal();
} else {
List<String> caches = cacheEvent.getClearCaches();
if (caches != null) {
for (String cache : caches) {
try {
serverCacheManager.clearLocal(Class.forName(cache));
} catch (Exception e) {
logger.error("Error clearing local cache for type " + cache, e);
}
}
}
}
}
}
private <P> P executeInTrans(Function<SpiTransaction, P> fun, Transaction t) {
@@ -2095,7 +2035,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (t != null) {
return new ObtainedTransaction((SpiTransaction) t);
}
SpiTransaction trans = transactionScopeManager.get();
SpiTransaction trans = transactionManager.getActive();
if (trans != null) {
return new ObtainedTransaction(trans);
}
@@ -2105,9 +2045,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public SpiTransaction beginServerTransaction() {
SpiTransaction t = transactionManager.createTransaction(0, false, -1);
transactionScopeManager.set(t);
return t;
return transactionManager.beginServerTransaction();
}
@Override
@@ -2124,35 +2062,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public CallStack createCallStack() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// ignore the first 6 as they are always avaje stack elements
int startIndex = IGNORE_LEADING_ELEMENTS;
// find the first non-avaje stackElement
for (; startIndex < stackTrace.length; startIndex++) {
if (!stackTrace[startIndex].getClassName().startsWith(IO_EBEAN)) {
break;
}
}
int stackLength = stackTrace.length - startIndex;
if (stackLength > maxCallStack) {
// maximum of maxCallStack stackTrace elements
stackLength = maxCallStack;
}
// create the 'interesting' part of the stackTrace
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
if (stackLength < 1) {
// this should not really happen
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
}
return callStackFactory.createCallStack(finalTrace);
return callStackFactory.createCallStack();
}
@Override
@@ -2194,4 +2104,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
}
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return transactionManager.collectTransactionStatistics(reset);
}
}
@@ -371,10 +371,11 @@ public class InternalConfiguration {
*/
public TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean localL2 = cacheManager.isLocalL2Caching();
TransactionManagerOptions options =
new TransactionManagerOptions(localL2, serverConfig, clusterManager, backgroundExecutor,
new TransactionManagerOptions(localL2, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
if (serverConfig.isExplicitTransactionBeginMode()) {
@@ -410,11 +411,11 @@ public class InternalConfiguration {
case DB:
return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
case SCHEMA:
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantSchemaProvider());
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantSchemaProvider());
case CATALOG:
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantCatalogProvider());
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantCatalogProvider());
default:
return new SimpleDataSourceProvider(serverConfig.getDataSource());
return new SimpleDataSourceProvider(serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource());
}
}
@@ -433,18 +434,17 @@ public class InternalConfiguration {
/**
* Create the TransactionScopeManager taking into account JTA or external transaction manager.
*/
public TransactionScopeManager createTransactionScopeManager(TransactionManager transactionManager) {
public TransactionScopeManager createTransactionScopeManager() {
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
externalTransactionManager = new JtaTransactionManager();
}
if (externalTransactionManager != null) {
externalTransactionManager.setTransactionManager(transactionManager);
logger.info("Using Transaction Manager [" + externalTransactionManager.getClass() + "]");
return new ExternalTransactionScopeManager(transactionManager, externalTransactionManager);
return new ExternalTransactionScopeManager(serverConfig.getName(), externalTransactionManager);
} else {
return new DefaultTransactionScopeManager(transactionManager);
return new DefaultTransactionScopeManager(serverConfig.getName());
}
}
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
*/
public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
private final CurrentTenantProvider tenantProvider;
private final DataSource dataSource;
private final TenantCatalogProvider catalogProvider;
private final DataSource readOnlyDataSource;
private final CatalogDataSource catalogDataSource;
private final CatalogDataSource readOnly;
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantCatalogProvider catalogProvider) {
this.tenantProvider = tenantProvider;
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantCatalogProvider catalogProvider) {
this.dataSource = dataSource;
this.catalogProvider = catalogProvider;
this.catalogDataSource = new CatalogDataSource();
this.readOnlyDataSource = readOnlyDataSource;
this.catalogDataSource = new CatalogDataSource(dataSource, tenantProvider, catalogProvider);
if (readOnlyDataSource == null) {
this.readOnly = null;
} else {
this.readOnly = new CatalogDataSource(readOnlyDataSource, tenantProvider, catalogProvider);
}
}
@Override
@@ -37,28 +39,48 @@ public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
return catalogDataSource;
}
@Override
public DataSource getReadOnlyDataSource() {
return readOnly;
}
@Override
public Connection getConnection(Object tenantId) throws SQLException {
return catalogDataSource.getConnectionForTenant(tenantId);
}
@Override
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
return readOnly.getConnectionForTenant(tenantId);
}
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool) {
((DataSourcePool) readOnlyDataSource).shutdown(false);
}
if (dataSource instanceof DataSourcePool) {
((DataSourcePool) dataSource).shutdown(deregisterDriver);
}
}
/**
* Returns the DB catalog for the current user Tenant Id.
*/
private String tenantCatalog() {
return catalogProvider.catalog(tenantProvider.currentId());
}
private class CatalogDataSource implements DataSource {
CatalogDataSource() {
final DataSource dataSource;
final CurrentTenantProvider tenantProvider;
final TenantCatalogProvider catalogProvider;
CatalogDataSource(DataSource dataSource, CurrentTenantProvider tenantProvider, TenantCatalogProvider catalogProvider) {
this.dataSource = dataSource;
this.tenantProvider = tenantProvider;
this.catalogProvider = catalogProvider;
}
/**
* Returns the DB catalog for the current user Tenant Id.
*/
private String tenantCatalog() {
return catalogProvider.catalog(tenantProvider.currentId());
}
/**
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
*/
class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
private final CurrentTenantProvider tenantProvider;
private final DataSource dataSource;
private final TenantSchemaProvider schemaProvider;
private final DataSource readOnlyDataSource;
private final SchemaDataSource schemaDataSource;
private final SchemaDataSource readOnly;
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantSchemaProvider schemaProvider) {
this.tenantProvider = tenantProvider;
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantSchemaProvider schemaProvider) {
this.dataSource = dataSource;
this.schemaProvider = schemaProvider;
this.schemaDataSource = new SchemaDataSource();
this.readOnlyDataSource = readOnlyDataSource;
this.schemaDataSource = new SchemaDataSource(dataSource, schemaProvider, tenantProvider);
if (readOnlyDataSource == null) {
this.readOnly = null;
} else {
this.readOnly = new SchemaDataSource(readOnlyDataSource, schemaProvider, tenantProvider);
}
}
@Override
@@ -37,28 +39,44 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
return schemaDataSource;
}
@Override
public DataSource getReadOnlyDataSource() {
return readOnly;
}
@Override
public Connection getConnection(Object tenantId) throws SQLException {
return schemaDataSource.getConnectionForTenant(tenantId);
}
@Override
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
return readOnly.getConnectionForTenant(tenantId);
}
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool) {
((DataSourcePool) readOnlyDataSource).shutdown(false);
}
if (dataSource instanceof DataSourcePool) {
((DataSourcePool) dataSource).shutdown(deregisterDriver);
}
}
/**
* Returns the DB schema for the current user Tenant Id.
* Tenant schema aware DataSource.
*/
private String tenantSchema() {
return schemaProvider.schema(tenantProvider.currentId());
}
private static class SchemaDataSource implements DataSource {
private class SchemaDataSource implements DataSource {
private final DataSource dataSource;
private final TenantSchemaProvider schemaProvider;
private final CurrentTenantProvider tenantProvider;
SchemaDataSource() {
SchemaDataSource(DataSource dataSource, TenantSchemaProvider schemaProvider, CurrentTenantProvider tenantProvider) {
this.dataSource = dataSource;
this.schemaProvider = schemaProvider;
this.tenantProvider = tenantProvider;
}
/**
@@ -70,6 +88,13 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
return connection;
}
/**
* Returns the DB schema for the current user Tenant Id.
*/
private String tenantSchema() {
return schemaProvider.schema(tenantProvider.currentId());
}
/**
* Return the connection with the appropriate DB schema set.
*/
@@ -22,6 +22,12 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
this.dataSourceProvider = dataSourceProvider;
}
@Override
public DataSource getReadOnlyDataSource() {
// read only datasource not supported with DB per tenant at this stage
return null;
}
@Override
public DataSource getDataSource() {
return dataSourceProvider.dataSource(tenantProvider.currentId());
@@ -32,6 +38,11 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
return dataSourceProvider.dataSource(tenantId).getConnection();
}
@Override
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
throw new SQLException("Not currently supported");
}
@Override
public void shutdown(boolean deregisterDriver) {
dataSourceProvider.shutdown(deregisterDriver);
@@ -0,0 +1,16 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.CallStack;
/**
* A CallStackFactory we can use when we don't use AutoTune.
*/
class NoopCallStackFactory implements CallStackFactory {
private final CallStack COMMON = new CallStack(Thread.currentThread().getStackTrace(), 0, 0);
@Override
public CallStack createCallStack() {
return COMMON;
}
}
@@ -14,9 +14,12 @@ import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.BeanQueryRequest;
import io.ebean.text.json.JsonReadOptions;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.NaturalKeySet;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
@@ -30,9 +33,6 @@ import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.NaturalKeySet;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -96,6 +96,20 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public void profileLocationById() {
if (query.getProfileLocation() == null) {
query.setProfileLocation(beanDescriptor.profileLocationById());
}
}
@Override
public void profileLocationAll() {
if (query.getProfileLocation() == null && query.isFindAll()) {
query.setProfileLocation(beanDescriptor.profileLocationAll());
}
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
@@ -118,8 +132,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
* Return the database platform like clause.
*/
@Override
public String getDBLikeClause() {
return ebeanServer.getDatabasePlatform().getLikeClause();
public String getDBLikeClause(boolean rawLikeExpression) {
return ebeanServer.getDatabasePlatform().getLikeClause(rawLikeExpression);
}
/**
@@ -241,13 +255,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public void initTransIfRequired() {
// first check if the query requires its own transaction
if (transaction == null) {
// maybe a current one
transaction = ebeanServer.currentServerTransaction();
if (transaction == null) {
if (query.getType().isUpdate()) {
// bulk update or delete query
transaction = ebeanServer.beginServerTransaction();
} else {
// create an implicit transaction to execute this query
// potentially using read-only DataSource with autoCommit
transaction = ebeanServer.createQueryTransaction(query.getTenantId());
createdTransaction = true;
}
createdTransaction = true;
}
persistenceContext = getPersistenceContext(query, transaction);
loadContext = new DLoadContext(this, secondaryQueries);
@@ -503,12 +519,16 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
}
@Override
public void resetBeanCacheAutoMode() {
query.resetBeanCacheAutoMode();
public void resetBeanCacheAutoMode(boolean findOne) {
query.resetBeanCacheAutoMode(findOne);
}
public boolean isQueryCachePut() {
return cacheKey != null && query.getUseQueryCache().isPut();
}
public boolean isBeanCachePut() {
return query.isBeanCachePut();
return !transaction.isSkipCache() && query.isBeanCachePut();
}
/**
@@ -580,7 +600,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
@SuppressWarnings("unchecked")
public Object getFromQueryCache() {
if (query.getUseQueryCache() == CacheMode.OFF) {
if (query.getUseQueryCache() == CacheMode.OFF || (transaction != null && transaction.isSkipCache())) {
return null;
} else {
cacheKey = query.queryHash();
@@ -61,7 +61,7 @@ public final class RelationalQueryRequest {
/**
* Create the BeanFindRequest.
*/
public RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
this.ebeanServer = server;
this.queryEngine = engine;
this.query = (SpiSqlQuery) q;
@@ -14,8 +14,11 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
private final DataSource dataSource;
SimpleDataSourceProvider(DataSource dataSource) {
private final DataSource readOnlyDataSource;
SimpleDataSourceProvider(DataSource dataSource, DataSource readOnlyDataSource) {
this.dataSource = dataSource;
this.readOnlyDataSource = readOnlyDataSource;
}
@Override
@@ -23,13 +26,26 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
return dataSource;
}
@Override
public DataSource getReadOnlyDataSource() {
return readOnlyDataSource;
}
@Override
public Connection getConnection(Object tenantId) throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
return readOnlyDataSource.getConnection();
}
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool){
((DataSourcePool) readOnlyDataSource).shutdown(false);
}
if (dataSource instanceof DataSourcePool){
((DataSourcePool) dataSource).shutdown(deregisterDriver);
}
@@ -134,12 +134,12 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* Reset Bean cache mode AUTO - require explicit setting for bean cache use with findList().
*/
void resetBeanCacheAutoMode();
void resetBeanCacheAutoMode(boolean findOne);
/**
* Return the Database platform like clause.
*/
String getDBLikeClause();
String getDBLikeClause(boolean rawLikeExpression);
/**
* Escapes a string to use it as exact match in Like clause.
@@ -155,4 +155,14 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
* Return true if this query is expected to use the doc store.
*/
boolean isUseDocStore();
/**
* Set profile location for "find by id" if not set.
*/
void profileLocationById();
/**
* Set profile location for "find all" if not set.
*/
void profileLocationAll();
}
@@ -22,7 +22,7 @@ public class BeanCascadeInfo {
}
}
private void setType(CascadeType type) {
public void setType(CascadeType type) {
switch (type) {
case ALL:
save = true;
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
@@ -27,12 +28,12 @@ import io.ebean.event.changelog.ChangeType;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.meta.MetaBeanInfo;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.plugin.BeanDocType;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
import io.ebean.plugin.Property;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.LoadContext;
@@ -61,9 +62,7 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.text.json.ReadJson;
@@ -100,7 +99,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
public class BeanDescriptor<T> implements BeanType<T> {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -119,6 +118,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final Map<String, String> namedQuery;
private final short profileBeanId;
private final ProfileLocation locationById;
private final ProfileLocation locationAll;
private final boolean multiValueSupported;
@@ -178,6 +179,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final String baseTableAsOf;
private final String baseTableVersionsBetween;
private final boolean historySupport;
private final TableJoin primaryKeyJoin;
private final BeanProperty softDeleteProperty;
private final boolean softDelete;
@@ -306,6 +308,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final BeanPropertyAssocOne<?> unidirectional;
private final BeanProperty orderColumn;
/**
* list of properties that are Lists/Sets/Maps (Derived).
@@ -415,6 +418,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.name = InternString.intern(deploy.getName());
this.baseTableAlias = "t0";
this.fullName = InternString.intern(deploy.getFullName());
this.locationById = ProfileLocation.createAt(fullName+".byId");
this.locationAll = ProfileLocation.createAt(fullName+".all");
this.profileBeanId = deploy.getProfileId();
this.beanType = deploy.getBeanType();
this.rootBeanType = PersistenceContextUtil.root(beanType);
@@ -451,6 +456,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.draftTable = deploy.getDraftTable();
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.primaryKeyJoin = deploy.getPrimaryKeyJoin();
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
this.dependentTables = deploy.getDependentTables();
this.dbComment = deploy.getDbComment();
@@ -475,6 +481,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
this.propertiesLocal = listHelper.getLocal();
this.propertiesMutable = listHelper.getMutable();
this.unidirectional = listHelper.getUnidirectional();
this.orderColumn = listHelper.getOrderColumn();
this.propertiesOne = listHelper.getOnes();
this.propertiesOneExportedSave = listHelper.getOneExportedSave();
this.propertiesOneExportedDelete = listHelper.getOneExportedDelete();
@@ -537,6 +544,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
/**
* Return a location for "find by id".
*/
public ProfileLocation profileLocationById() {
return locationById;
}
/**
* Return a location for "find all".
*/
public ProfileLocation profileLocationAll() {
return locationAll;
}
/**
* Return the id used in profiling to identify the bean type.
*/
@@ -1348,6 +1369,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
cacheHelp.beanCachePut(bean);
}
@SuppressWarnings("unchecked")
public void cacheBeanPutAll(Collection<?> beans) {
if (!beans.isEmpty()) {
cacheHelp.beanPutAll((Collection<EntityBean>)beans);
}
}
void cacheBeanPutAllDirect(Collection<EntityBean> beans) {
cacheHelp.beanCachePutAllDirect(beans);
}
/**
* Put a bean into the cache as the correct type.
*/
@@ -1517,25 +1549,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return new DeployUpdateParser(this).parse(ormUpdateStatement);
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
@Override
public List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) {
return collectQueryPlanStatisticsInternal(reset, false);
}
public List<MetaQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) {
List<MetaQueryPlanStatistic> list = new ArrayList<>(queryPlanCache.size());
public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) {
for (CQueryPlan queryPlan : queryPlanCache.values()) {
Snapshot snapshot = queryPlan.getSnapshot(reset);
if (collectAll || snapshot.getExecutionCount() > 0) {
list.add(snapshot);
if (!queryPlan.isEmptyStats()) {
collector.add(queryPlan.getSnapshot(collector.isReset()));
}
}
return list;
}
/**
@@ -1908,6 +1927,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return owner.getBeanDescriptor(otherType);
}
/**
* Return the order column property.
*/
public BeanProperty getOrderColumn() {
return orderColumn;
}
/**
* Return the "shadow" property to support unidirectional relationships.
* <p>
@@ -2873,6 +2899,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
}
}
public TableJoin getPrimaryKeyJoin() {
return primaryKeyJoin;
}
@Override
public BeanProperty getIdProperty() {
return idProperty;
@@ -418,6 +418,22 @@ final class BeanDescriptorCacheHelp<T> {
return CachedBeanDataFromBean.extract(targetDesc, bean);
}
void beanPutAll(Collection<EntityBean> beans) {
if (desc.inheritInfo != null) {
Class<?> aClass = theClassOf(beans);
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
} else {
beanCachePutAllDirect(beans);
}
}
private Class<?> theClassOf(Collection<EntityBean> beans) {
if (beans instanceof List) {
return ((List)beans).get(0).getClass();
}
return beans.iterator().next().getClass();
}
/**
* Put a bean into the bean cache.
*/
@@ -430,9 +446,41 @@ final class BeanDescriptorCacheHelp<T> {
}
}
void beanCachePutAllDirect(Collection<EntityBean> beans) {
Map<Object,Object> natKeys = null;
if (naturalKey != null) {
natKeys = new LinkedHashMap<>();
}
Map<Object,Object> map = new LinkedHashMap<>();
for (EntityBean bean : beans) {
CachedBeanData beanData = beanExtractData(desc, bean);
Object id = desc.getId(bean);
map.put(id, beanData);
if (naturalKey != null) {
Object naturalKey = calculateNaturalKey(beanData);
if (naturalKey != null) {
natKeys.put(naturalKey, id);
}
}
}
if (beanLog.isDebugEnabled()) {
beanLog.debug(" PUT ALL {}({})", cacheName, map.keySet());
}
getBeanCache().putAll(map);
if (natKeys != null && !natKeys.isEmpty()) {
if (natLog.isDebugEnabled()) {
natLog.debug(" PUT ALL {}({}, {})", cacheName, naturalKey, natKeys.keySet());
}
naturalKeyCache.putAll(natKeys);
}
}
/**
* Put the bean into the bean cache.
*/
* Put the bean into the bean cache.
*/
void beanCachePutDirect(EntityBean bean) {
CachedBeanData beanData = beanExtractData(desc, bean);
@@ -494,6 +542,9 @@ final class BeanDescriptorCacheHelp<T> {
}
return null;
}
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET {}({}) - hit", cacheName, id);
}
return convertToBean(id, readOnly, context, data);
}
@@ -558,9 +609,6 @@ final class BeanDescriptorCacheHelp<T> {
ebi.setPersistenceContext(context);
desc.contextPut(context, id, bean);
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET {}({}) - hit", cacheName, id);
}
if (desc.isReadAuditing()) {
desc.readAuditBean("l2", "", bean);
}
@@ -38,6 +38,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployBeanTable;
import io.ebeaninternal.server.deploy.meta.DeployOrderColumn;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.parse.DeployBeanInfo;
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
@@ -50,6 +51,7 @@ import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.type.ScalarTypeInteger;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
@@ -797,8 +799,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// We only perform 'circular' checks etc after we have
// all the DeployBeanDescriptors created and in the map.
List<DeployBeanPropertyAssocOne<?>> primaryKeyJoinCheck = new ArrayList<>();
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
checkMappedBy(info);
checkMappedBy(info, primaryKeyJoinCheck);
}
for (DeployBeanPropertyAssocOne<?> prop : primaryKeyJoinCheck) {
checkUniDirectionalPrimaryKeyJoin(prop);
}
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
@@ -868,12 +874,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* relationships.
* </p>
*/
private void checkMappedBy(DeployBeanInfo<?> info) {
private void checkMappedBy(DeployBeanInfo<?> info, List<DeployBeanPropertyAssocOne<?>> primaryKeyJoinCheck) {
for (DeployBeanPropertyAssocOne<?> oneProp : info.getDescriptor().propertiesAssocOne()) {
if (!oneProp.isTransient()) {
if (oneProp.getMappedBy() != null) {
checkMappedByOneToOne(oneProp);
} else if (oneProp.isPrimaryKeyJoin()) {
primaryKeyJoinCheck.add(oneProp);
}
}
}
@@ -973,6 +981,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
throw new PersistenceException(msg);
}
private void makeOrderColumn(DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
DeployOrderColumn orderColumn = oneToMany.getOrderColumn();
DeployBeanProperty orderProperty = new DeployBeanProperty(targetDesc, Integer.class, ScalarTypeInteger.INSTANCE, null);
orderProperty.setName(DeployOrderColumn.LOGICAL_NAME);
orderProperty.setDbColumn(orderColumn.getName());
orderProperty.setNullable(orderColumn.isNullable());
orderProperty.setDbInsertable(orderColumn.isInsertable());
orderProperty.setDbUpdateable(orderColumn.isUpdatable());
orderProperty.setDbRead(true);
targetDesc.setOrderColumn(orderProperty);
}
/**
* A OneToMany with no matching mappedBy property in the target so must be
* unidirectional.
@@ -1071,6 +1096,21 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DeployTableJoin otherTableJoin = mappedAssocOne.getTableJoin();
otherTableJoin.copyWithoutType(tableJoin, true, tableJoin.getTable());
}
if (mappedAssocOne.isPrimaryKeyJoin()) {
// bi-directional PrimaryKeyJoin ...
mappedAssocOne.setPrimaryKeyJoin(false);
prop.setPrimaryKeyExport();
addPrimaryKeyJoin(prop);
}
}
private void checkUniDirectionalPrimaryKeyJoin(DeployBeanPropertyAssocOne<?> prop) {
if (prop.isPrimaryKeyJoin()) {
// uni-directional PrimaryKeyJoin ...
prop.setPrimaryKeyExport();
addPrimaryKeyJoin(prop);
}
}
/**
@@ -1091,6 +1131,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
prop.getCascadeInfo().setSaveDelete(true, true);
}
if (prop.hasOrderColumn()) {
makeOrderColumn(prop);
}
if (prop.getMappedBy() == null) {
// if we are doc store only we are done
// this allowes the use of @OneToMany in @DocStore - Entities
@@ -1392,7 +1436,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Integer pos = reflectProps.getPropertyIndex(propName);
if (pos == null) {
if (isPersistentField(prop)) {
throw new IllegalStateException("Property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
throw new IllegalStateException(
"If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " +
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
}
} else {
@@ -1544,6 +1590,17 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return changeLogListener;
}
public void addPrimaryKeyJoin(DeployBeanPropertyAssocOne<?> prop) {
String baseTable = prop.getDesc().getBaseTable();
DeployTableJoin inverse = prop.getTableJoin().createInverse(baseTable);
TableJoin inverseJoin = new TableJoin(inverse);
DeployBeanInfo<?> target = deployInfoMap.get(prop.getTargetType());
target.setPrimaryKeyJoin(inverseJoin);
}
/**
* Comparator to sort the BeanDescriptors by name.
*/
@@ -1,7 +1,11 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.id.ImportedId;
@@ -10,7 +14,7 @@ import io.ebeaninternal.server.deploy.id.ImportedIdSimple;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
@@ -402,17 +406,83 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
throw new PersistenceException(msg);
}
protected void bindWhereParentId(List<Object> bindValues, Object parentId) {
private List<Object> flattenParentIds(List<Object> parentIds) {
List<Object> bindValues = new ArrayList<>(parentIds.size() * 3);
for (Object parentId : parentIds) {
flatten(bindValues, parentId);
}
return bindValues;
}
if (exportedProperties.length == 1) {
private List<Object> flattenParentId(Object parentId) {
List<Object> bindValues = new ArrayList<>();
flatten(bindValues, parentId);
return bindValues;
}
private void flatten(List<Object> bindValues, Object parentId) {
if (isExportedSimple()) {
bindValues.add(parentId);
} else {
EntityBean parent = (EntityBean) parentId;
for (ExportedProperty exportedProperty : exportedProperties) {
Object embVal = exportedProperty.getValue(parent);
bindValues.add(embVal);
bindValues.add(exportedProperty.getValue(parent));
}
}
}
EbeanServer server() {
return getBeanDescriptor().getEbeanServer();
}
void bindParentIds(DefaultSqlUpdate delete, List<Object> parentIds) {
if (isExportedSimple()) {
delete.addParameter(new MultiValueWrapper(parentIds));
} else {
// embedded ids etc
List<Object> bindValues = flattenParentIds(parentIds);
for (Object bindValue : bindValues) {
delete.addParameter(bindValue);
}
}
}
void bindParentId(DefaultSqlUpdate sqlUpd, Object parentId) {
if (isExportedSimple()) {
sqlUpd.addParameter(parentId);
return;
}
EntityBean parent = (EntityBean) parentId;
for (ExportedProperty exportedProperty : exportedProperties) {
sqlUpd.addParameter(exportedProperty.getValue(parent));
}
}
void bindParentIdEq(String expr, Object parentId, Query<?> q) {
if (isExportedSimple()) {
q.where().raw(expr, parentId);
} else {
// embedded ids etc
List<Object> bindValues = flattenParentId(parentId);
q.where().raw(expr, bindValues.toArray());
}
}
void bindParentIdsIn(String expr, List<Object> parentIds, Query<?> q) {
if (isExportedSimple()) {
q.where().raw(expr, new MultiValueWrapper(parentIds));
} else {
// embedded ids etc
List<Object> bindValues = flattenParentIds(parentIds);
q.where().raw(expr, bindValues.toArray());
}
}
private boolean isExportedSimple() {
return exportedProperties.length == 1;
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Expression;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
@@ -18,7 +17,6 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -60,6 +58,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private final boolean unidirectional;
/**
* Flag to indicate that the target has a order column to auto populate.
*/
private final boolean hasOrderColumn;
/**
* Flag to indicate manyToMany relationship.
*/
@@ -111,6 +114,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public BeanPropertyAssocMany(BeanDescriptor<?> descriptor, DeployBeanPropertyAssocMany<T> deploy) {
super(descriptor, deploy);
this.unidirectional = deploy.isUnidirectional();
this.hasOrderColumn = deploy.hasOrderColumn();
this.manyToMany = deploy.isManyToMany();
this.manyType = deploy.getManyType();
this.mapKey = deploy.getMapKey();
@@ -290,7 +294,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
bindWhereParendId(sqlDelete, parentId);
bindParentId(sqlDelete, parentId);
return sqlDelete;
}
@@ -308,18 +312,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false, "");
List<Object> bindValues = new ArrayList<>();
bindWhereParentId(bindValues, parentId);
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
.where()
.raw(rawWhere, bindValues.toArray())
.query();
EbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdEq(rawWhere, parentId, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds);
q.where().not(idIn);
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
@@ -334,16 +333,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return false;
}
/**
* Add the loaded current bean to its associated parent.
*/
public void lazyLoadMany(EntityBean current) {
EntityBean parentBean = childMasterProperty.getValueAsEntityBean(current);
if (parentBean != null) {
addBeanToCollectionWithCreate(parentBean, current, true);
}
}
public void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds, boolean useDocStore) {
if (useDocStore) {
// assumes the ManyToOne property is included
@@ -365,59 +354,35 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
// Flatten the bind values if needed (embeddedId)
List<Object> bindValues = getBindParentIds(parentIds);
if (descriptor.isSimpleId()) {
query.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
query.where().raw(expr, bindValues.toArray());
}
bindParentIdsIn(expr, parentIds, query);
}
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIdList.size(), exportedPropertyBindProto);
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
List<Object> bindValues = new ArrayList<>();
for (Object aParentIdList : parentIdList) {
bindWhereParentId(bindValues, aParentIdList);
}
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType());
if (descriptor.isSimpleId()) {
q.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
q.where().raw(expr, bindValues.toArray());
}
EbeanServer server = descriptor.getEbeanServer();
Query<?> q = server.find(propertyType);
bindParentIdsIn(expr, parentIds, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds);
q.where().not(idIn);
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
private SqlUpdate deleteByParentIdList(List<Object> parentIdList) {
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
String inClause = buildInClauseBinding(parentIdList.size(), exportedPropertyBindProto);
sb.append(inClause);
sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
if (exportedProperties.length == 1) {
bindWhereParendId(delete, new MultiValueWrapper(parentIdList));
} else {
for (Object aParentIdist : parentIdList) {
bindWhereParendId(delete, aParentIdist);
}
}
bindParentIds(delete, parentIds);
return delete;
}
@@ -506,22 +471,34 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
StringBuilder sb = new StringBuilder();
boolean softDelete = targetDescriptor.isSoftDelete();
StringBuilder sb = new StringBuilder(50);
SpiQuery<?> query = request.getQueryRequest().getQuery();
if (manyToMany) {
sb.append(query.isAsDraft() ? intersectionDraftTable : intersectionPublishTable);
} else {
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
}
sb.append(" x where ");
if (softDelete && manyToMany) {
sb.append(" x join ");
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
sb.append(" x2 on ");
inverseJoin.addJoin("x2", "x", sb);
} else {
sb.append(" x");
}
sb.append(" where ");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(" and ");
}
exportedProperties[i].appendWhere(sb, "x.", path);
}
if (targetDescriptor.isSoftDelete()) {
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate("x"));
if (softDelete) {
String alias = (manyToMany) ? "x2" : "x";
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias));
}
return sb.toString();
}
@@ -563,6 +540,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return true;
}
public boolean hasOrderColumn() {
return hasOrderColumn;
}
@Override
public boolean isAssocMany() {
return true;
@@ -678,33 +659,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return descriptor.getId(parentBean);
}
public List<Object> getBindParentIds(List<Object> parentIds) {
if (exportedProperties.length == 1) {
return parentIds;
}
List<Object> expandedList = new ArrayList<>(parentIds.size() * exportedProperties.length);
for (Object parentId : parentIds) {
for (ExportedProperty exportedProperty : exportedProperties) {
Object compId = parentId;
expandedList.add(exportedProperty.getValue((EntityBean) compId));
}
}
return expandedList;
}
private void bindWhereParendId(DefaultSqlUpdate sqlUpd, Object parentId) {
if (exportedProperties.length == 1) {
sqlUpd.addParameter(parentId);
return;
}
EntityBean parent = (EntityBean) parentId;
for (ExportedProperty exportedProperty : exportedProperties) {
Object embVal = exportedProperty.getValue(parent);
sqlUpd.addParameter(embVal);
}
}
public void addSelectExported(DbSqlContext ctx, String tableAlias) {
String alias = manyToMany ? "int_" : tableAlias;
@@ -7,6 +7,7 @@ import io.ebean.Transaction;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
@@ -14,7 +15,6 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.text.json.ReadJson;
@@ -39,15 +39,14 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
private final boolean importedPrimaryKey;
private final boolean primaryKeyExport;
private AssocOneHelp localHelp;
protected final BeanProperty[] embeddedProps;
private final HashMap<String, BeanProperty> embeddedPropsMap;
/**
* The information for Imported foreign Keys.
*/
protected ImportedId importedId;
private String deleteByParentIdSql;
@@ -70,6 +69,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
super(descriptor, deploy);
primaryKeyExport = deploy.isPrimaryKeyExport();
importedPrimaryKey = deploy.isImportedPrimaryKey();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
@@ -202,38 +202,29 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
private SqlUpdate deleteByParentIdList(List<Object> parentIdist) {
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdist.size());
sb.append(inClause);
sb.append(targetIdBinder.getIdInValueExpr(false, parentIds.size()));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
for (Object aParentIdist : parentIdist) {
targetIdBinder.bindId(delete, aParentIdist);
}
bindParentIds(delete, parentIds);
return delete;
}
private SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate delete = new DefaultSqlUpdate(deleteByParentIdSql);
if (exportedProperties.length == 1) {
delete.addParameter(parentId);
} else {
targetDescriptor.getIdBinder().bindId(delete, parentId);
}
bindParentId(delete, parentId);
return delete;
}
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdist, Transaction t) {
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIds, Transaction t) {
if (parentId != null) {
return findIdsByParentId(parentId, t);
} else {
return findIdsByParentIdList(parentIdist, t);
return findIdsByParentIdList(parentIds, t);
}
}
@@ -241,33 +232,21 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
String rawWhere = deriveWhereParentIdSql(false);
List<Object> bindValues = new ArrayList<>();
bindWhereParentId(bindValues, parentId);
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
.where()
.raw(rawWhere, bindValues.toArray())
.query();
EbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdEq(rawWhere, parentId, q);
return server.findIds(q, t);
}
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t) {
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdList.size());
String inClause = targetIdBinder.getIdInValueExpr(false, parentIds.size());
String expr = rawWhere + inClause;
List<Object> bindValues = new ArrayList<>();
for (Object aParentIdList : parentIdList) {
bindWhereParentId(bindValues, aParentIdList);
}
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
.where().raw(expr, bindValues.toArray()).query();
EbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdsIn(expr, parentIds, q);
return server.findIds(q, t);
}
@@ -571,13 +550,17 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (!isTransient) {
localHelp.appendSelect(ctx, subQuery);
if (primaryKeyExport) {
descriptor.getIdProperty().appendSelect(ctx, subQuery);
} else {
localHelp.appendSelect(ctx, subQuery);
}
}
}
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (!isTransient) {
if (!isTransient && !primaryKeyExport) {
localHelp.appendFrom(ctx, joinType);
if (sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
@@ -755,6 +738,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
*/
public void setParentBeanToChild(EntityBean parent, EntityBean child) {
if (primaryKeyExport) {
Object parentId = descriptor.getId(parent);
targetDescriptor.convertSetId(parentId, child);
}
if (mappedBy != null) {
BeanProperty beanProperty = targetDescriptor.getBeanProperty(mappedBy);
if (beanProperty != null && beanProperty.getValue(child) == null) {
@@ -0,0 +1,36 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Bean property for synthetic sort order value / order column.
*
* The value of which is held on the entity bean intercept.
*/
public class BeanPropertyOrderColumn extends BeanProperty {
public BeanPropertyOrderColumn(BeanDescriptor<?> descriptor, DeployBeanProperty deploy) {
super(descriptor, deploy);
}
@Override
public Object getValue(EntityBean bean) {
return bean._ebean_getIntercept().getSortOrder();
}
@Override
public Object getValueIntercept(EntityBean bean) {
return bean._ebean_getIntercept().getSortOrder();
}
@Override
public void setValue(EntityBean bean, Object value) {
bean._ebean_getIntercept().setSortOrder((int)value);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
bean._ebean_getIntercept().setSortOrder((int)value);
}
}
@@ -121,4 +121,13 @@ public class BeanTable {
}
/**
* Return the primary key DB column.
*/
public String getIdColumn() {
if (idProperties.length != 1) {
throw new IllegalStateException("Expecting only one Id column to join to on "+beanType);
}
return idProperties[0].dbColumn;
}
}
@@ -37,6 +37,8 @@ public abstract class DeployParser {
protected int pos;
protected String priorWord;
protected String word;
protected char wordTerminator;
@@ -45,7 +47,6 @@ public abstract class DeployParser {
public abstract String getDeployWord(String expression);
/**
* Return the join includes.
*/
@@ -67,8 +68,14 @@ public abstract class DeployParser {
this.sb = new StringBuilder(source.length() + 20);
while (nextWord()) {
String deployWord = convertWord();
sb.append(deployWord);
if (skipWordConvert()) {
sb.append(word);
priorWord = word;
} else {
String deployWord = convertWord();
sb.append(deployWord);
priorWord = deployWord;
}
if (pos < sourceLength) {
sb.append(wordTerminator);
if (wordTerminator == SINGLE_QUOTE) {
@@ -80,6 +87,10 @@ public abstract class DeployParser {
return sb.toString();
}
protected boolean skipWordConvert() {
return false;
}
private boolean nextWord() {
if (!findWordStart()) {
@@ -14,12 +14,15 @@ import java.util.Set;
*/
public final class DeployPropertyParser extends DeployParser {
private static final String JOIN = "join";
private static final String FROM = "from";
private final BeanDescriptor<?> beanDescriptor;
private final Set<String> includes = new HashSet<>();
public DeployPropertyParser(BeanDescriptor<?> beanDescriptor) {
DeployPropertyParser(BeanDescriptor<?> beanDescriptor) {
this.beanDescriptor = beanDescriptor;
}
@@ -28,6 +31,13 @@ public final class DeployPropertyParser extends DeployParser {
return includes;
}
/**
* Skip if in raw sql expression with from tableName or join tableName.
*/
protected boolean skipWordConvert() {
return FROM.equalsIgnoreCase(priorWord) || JOIN.equalsIgnoreCase(priorWord);
}
@Override
public String getDeployWord(String expression) {
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(expression);
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.deploy;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.query.SqlJoinType;
/**
@@ -145,4 +145,15 @@ public final class TableJoin {
return joinType.autoToOuter(type);
}
public void addJoin(String a1, String a2, StringBuilder sb) {
for (int i = 0; i < columns.length; i++) {
TableJoinColumn pair = columns[i];
if (i > 0) {
sb.append(" and ");
}
sb.append(a1).append(".").append(pair.getLocalDbColumn());
sb.append(" = ");
sb.append(a2).append(".").append(pair.getForeignDbColumn());
}
}
}

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