Compare commits

...
Author SHA1 Message Date
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
149 changed files with 3230 additions and 688 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
+21 -13
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.8.1</version>
<version>11.10.4</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.8.1</tag>
<tag>ebean-11.10.4</tag>
</scm>
<profiles>
@@ -103,7 +103,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.5</version>
<version>3.6</version>
</dependency>
<dependency>
@@ -112,22 +112,16 @@
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.5</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.2.1</version>
<version>11.3.1</version>
</dependency>
<dependency>
@@ -204,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>
@@ -214,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>
@@ -287,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>
+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;
}
}
@@ -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);
/**
@@ -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"));
@@ -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();
}
@@ -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);
}
@@ -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;
@@ -174,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.
@@ -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);
@@ -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";
}
}
@@ -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);
}
@@ -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);
@@ -331,6 +331,7 @@ public class DefaultContainer implements SpiContainer {
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,6 +61,7 @@ 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;
@@ -92,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;
@@ -130,18 +130,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final TransactionManager transactionManager;
private final TransactionScopeManager transactionScopeManager;
private final DataTimeZone dataTimeZone;
private final CallStackFactory callStackFactory;
/**
* Ebean defaults this to true but for EJB compatible behaviour set this to
* false;
*/
private final boolean rollbackOnChecked;
/**
* Handles the save, delete, updateSql CallableSql.
*/
@@ -249,8 +241,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
this.callStackFactory = initCallStackFactory(serverConfig);
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
this.relationalQueryEngine = config.createRelationalQueryEngine();
@@ -265,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();
@@ -563,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);
}
/**
@@ -629,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);
@@ -668,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);
}
/**
@@ -680,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
@@ -691,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();
@@ -713,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();
@@ -728,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);
@@ -772,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);
}
/**
@@ -785,7 +725,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public SpiTransaction currentServerTransaction() {
return transactionScopeManager.get();
return transactionManager.getActive();
}
/**
@@ -804,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);
}
/**
@@ -863,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;
@@ -879,7 +773,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public Transaction currentTransaction() {
return transactionScopeManager.get();
return transactionManager.getActive();
}
@Override
@@ -892,7 +786,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void commitTransaction() {
transactionScopeManager.commit();
transactionManager.scope().commit();
}
/**
@@ -900,7 +794,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void rollbackTransaction() {
transactionScopeManager.rollback();
transactionManager.scope().rollback();
}
/**
@@ -933,7 +827,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void endTransaction() {
transactionScopeManager.end();
transactionManager.scope().end();
}
/**
@@ -1114,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
@@ -1123,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());
@@ -1203,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);
}
@@ -1224,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);
}
@@ -1527,6 +1427,7 @@ 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);
request.profileLocationAll();
request.resetBeanCacheAutoMode(findOne);
Object result = request.getFromQueryCache();
if (result != null) {
@@ -1585,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();
@@ -1973,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) {
@@ -2127,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);
}
@@ -2137,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
@@ -2198,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()) {
@@ -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());
}
}
@@ -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,19 +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;
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;
}
persistenceContext = getPersistenceContext(query, transaction);
loadContext = new DLoadContext(this, secondaryQueries);
@@ -513,8 +523,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
query.resetBeanCacheAutoMode(findOne);
}
public boolean isQueryCachePut() {
return cacheKey != null && query.getUseQueryCache().isPut();
}
public boolean isBeanCachePut() {
return query.isBeanCachePut();
return !transaction.isSkipCache() && query.isBeanCachePut();
}
/**
@@ -586,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;
@@ -139,7 +139,7 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* 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();
}
@@ -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,8 +28,6 @@ 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;
@@ -63,7 +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.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;
@@ -415,6 +416,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);
@@ -537,6 +540,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.
*/
@@ -1528,25 +1545,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;
}
/**
@@ -1392,7 +1392,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 {
@@ -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;
@@ -290,7 +288,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 +306,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 +327,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 +348,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 +465,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();
}
@@ -678,33 +649,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;
@@ -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);
}
@@ -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());
}
}
}
@@ -112,7 +112,7 @@ public class DeployInheritInfo {
public int getColumnLength(InheritInfo parent) {
if (columnLength == 0) {
if (parent == null) {
columnLength = 10;
columnLength = 31;
} else {
columnLength = parent.getColumnLength();
}
@@ -112,30 +112,16 @@ public class DeployUtil {
if (!enumType.isEnum()) {
throw new IllegalArgumentException("Class [" + enumType + "] is Not a Enum?");
}
ScalarType<?> scalarType = typeManager.getScalarType(enumType);
if (enumOverrideDefaultMapping(enumerated, scalarType)) {
logger.debug("override default enum mapping for type {}", enumType);
scalarType = null;
}
if (scalarType == null) {
// look for @DbEnumValue or @EnumValue annotations etc
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) enumType;
EnumType type = enumerated != null ? enumerated.value() : null;
scalarType = typeManager.createEnumScalarType(enumClass, type);
}
prop.setScalarType(scalarType);
prop.setDbType(scalarType.getJdbcType());
}
try {
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) enumType;
EnumType type = enumerated != null ? enumerated.value() : null;
ScalarType<?> scalarType = typeManager.createEnumScalarType(enumClass, type);
prop.setScalarType(scalarType);
prop.setDbType(scalarType.getJdbcType());
/**
* Return true if there is an existing default mapping for the enum that needs
* to be overridden (for example, DayOfWeek defaults to Integer mapping 1 to 7
* and some might want this mapped to 'MONDAY' etc).
*/
private boolean enumOverrideDefaultMapping(Enumerated enumerated, ScalarType<?> scalarType) {
return enumerated != null && scalarType != null
&& enumerated.value() == EnumType.STRING
&& scalarType.getJdbcType() != Types.VARCHAR;
} catch (IllegalStateException e) {
throw new PersistenceException("Error mapping property " + prop.getFullBeanName() + " - " + e.getMessage());
}
}
/**
@@ -304,7 +290,7 @@ public class DeployUtil {
prop.setScalarType(scalarType);
}
public boolean isClobType(Class<?> type) {
private boolean isClobType(Class<?> type) {
return type.equals(String.class);
}
@@ -91,9 +91,9 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
* Append the database platform like clause.
*/
@Override
public void appendLike() {
public void appendLike(boolean rawLikeExpression) {
sql.append(" ");
sql.append(queryRequest.getDBLikeClause());
sql.append(queryRequest.getDBLikeClause(rawLikeExpression));
sql.append(" ");
}
@@ -54,7 +54,7 @@ class LikeExpression extends AbstractValueExpression {
request.append(" = ? ");
} else {
// append db platform like clause
request.appendLike();
request.appendLike(type == LikeType.RAW);
}
}
@@ -84,6 +84,10 @@ public class BatchedBeanHolder {
// with binding addBatch() for each request.
// Note updates and deletes can result in many PreparedStatements
// if their where clauses differ via use of IS NOT NULL.
if (deletes != null && !deletes.isEmpty()) {
control.executeNow(deletes);
deletes.clear();
}
if (inserts != null && !inserts.isEmpty()) {
control.executeNow(inserts);
inserts.clear();
@@ -92,10 +96,6 @@ public class BatchedBeanHolder {
control.executeNow(updates);
updates.clear();
}
if (deletes != null && !deletes.isEmpty()) {
control.executeNow(deletes);
deletes.clear();
}
persistedBeans.clear();
}
@@ -0,0 +1,47 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
/**
* Fixed / given location. Used internally for find by id and find all.
*/
class BasicProfileLocation implements ProfileLocation {
private final String location;
private final String shortDescription;
BasicProfileLocation(String location) {
this.location = location;
this.shortDescription = shortDesc(location);
}
public String toString() {
return shortDescription;
}
@Override
public void add(long executionTime) {
// do nothing
}
public String obtain() {
return location;
}
@Override
public String shortDescription() {
return shortDescription;
}
private String shortDesc(String location) {
int lastPer = location.lastIndexOf('.');
if (lastPer > -1) {
lastPer = location.lastIndexOf('.', lastPer-1);
if (lastPer > -1) {
return location.substring(lastPer+1);
}
}
return location;
}
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.profile;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
/**
* Default metric factory implementation.
*/
public class DMetricFactory implements MetricFactory {
@Override
public TimedMetricMap createTimedMetricMap(String name) {
return new DTimedMetricMap(name);
}
@Override
public TimedMetric createTimedMetric(String name) {
return new DTimedMetric(name);
}
}
@@ -0,0 +1,91 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
/**
* Default profile location that uses stack trace.
*/
class DProfileLocation implements ProfileLocation {
private static final String IO_EBEAN = "io.ebean";
private static final String UNKNOWN = "unknown";
private String location;
private String shortDescription;
private final int lineNumber;
DProfileLocation() {
this(0);
}
/**
* Create with a given line number.
*/
DProfileLocation(int lineNumber) {
this.lineNumber = lineNumber;
}
public String toString() {
return "location: " + location;
}
@Override
public void add(long executionTime) {
// do nothing
}
public String obtain() {
// atomic assignment so happy with this
if (location == null) {
location = create();
shortDescription = shortDesc(location);
}
return location;
}
@Override
public String shortDescription() {
return shortDescription;
}
private String create() {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (int i = 3; i < trace.length; i++) {
if (!trace[i].getClassName().startsWith(IO_EBEAN)) {
return withLineNumber(trace[i].toString());
}
}
return UNKNOWN;
}
private String withLineNumber(String traceLine) {
if (lineNumber == 0) {
return traceLine;
} else if (traceLine.endsWith(":1)")) {
return traceLine.substring(0, traceLine.length() - 3) + ":" + lineNumber + ")";
} else if (traceLine.contains(":")) {
return traceLine;
} else {
return traceLine.substring(0, traceLine.length() - 1) + ":" + lineNumber + ")";
}
}
private String shortDesc(String location) {
int pos = location.lastIndexOf('(');
if (pos == -1) {
pos = location.length();
}
pos = location.lastIndexOf('.', pos);
if (pos > -1) {
pos = location.lastIndexOf('.', pos - 1);
if (pos > -1) {
return location.substring(pos + 1);
}
}
return location;
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
/**
* Default implementation of the profile location factory.
*/
public class DProfileLocationFactory implements SpiProfileLocationFactory {
@Override
public ProfileLocation create() {
return new DProfileLocation();
}
@Override
public ProfileLocation create(int lineNumber, String label) {
TimedMetric timedMetric = MetricFactory.get().createTimedMetric("txn.named." + label);
DTimedProfileLocation loc = new DTimedProfileLocation(lineNumber, label, timedMetric);
TimedProfileLocationRegistry.register(loc);
return loc;
}
@Override
public ProfileLocation createAt(String location) {
return new BasicProfileLocation(location);
}
}
@@ -0,0 +1,100 @@
package io.ebeaninternal.server.profile;
import io.ebeaninternal.metric.TimedMetricStats;
/**
* Snapshot of the current statistics for a Counter or TimeCounter.
*/
class DTimeMetricStats implements TimedMetricStats {
private final String name;
private String location;
private final long startTime;
private final long count;
private final long total;
private final long max;
DTimeMetricStats(String name, long collectionStart, long count, long total, long max) {
this.name = name;
this.startTime = collectionStart;
this.count = count;
this.total = total;
// collection is racy so sanitize the max value if it has not been set
// this most likely would happen when count = 1 so max = mean
this.max = max != Long.MIN_VALUE ? max : (count < 1 ? 0 : Math.round(total / count));
}
public String toString() {
StringBuilder sb = new StringBuilder();
if (location != null) {
sb.append("loc:").append(location).append(" ");
}
if (name != null) {
sb.append("name:").append(name).append(" ");
}
sb.append("count:").append(count)
.append(" total:").append(total)
.append(" max:").append(max);
return sb.toString();
}
public void setLocation(String location) {
this.location = location;
}
@Override
public String getName() {
return name;
}
@Override
public String getLocation() {
return location;
}
/**
* Return the time the counter started statistics collection.
*/
@Override
public long getStartTime() {
return startTime;
}
/**
* Return the count of values collected.
*/
@Override
public long getCount() {
return count;
}
/**
* Return the total of all the values.
*/
@Override
public long getTotal() {
return total;
}
/**
* Return the Max value collected.
*/
@Override
public long getMax() {
return max;
}
/**
* Return the mean value rounded up.
*/
@Override
public long getMean() {
return (count < 1) ? 0L : Math.round((double)(total / count));
}
}
@@ -0,0 +1,127 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.metric.TimedMetric;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
/**
* Used to collect timed execution statistics.
* <p>
* It is intended for high concurrent updates to the statistics and relatively infrequent reads.
* </p>
*/
class DTimedMetric implements TimedMetric {
protected final String name;
protected final LongAdder count = new LongAdder();
protected final LongAdder total = new LongAdder();
protected final LongAccumulator max = new LongAccumulator(Math::max, Long.MIN_VALUE);
protected final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
DTimedMetric(String name) {
this.name = name;
}
/**
* Add a value. Usually the value is Time or Bytes etc.
*/
@Override
public void add(long value) {
count.increment();
total.add(value);
max.accumulate(value);
}
@Override
public boolean isEmpty() {
return count.sum() == 0;
}
@Override
public void collect(boolean reset, List<MetaTimedMetric> result) {
DTimeMetricStats metric = collect(reset);
if (metric != null) {
result.add(metric);
}
}
// @Override
public DTimeMetricStats collect(boolean reset) {
boolean empty = count.sum() == 0;
if (empty) {
if (reset) {
startTime.set(System.currentTimeMillis());
}
return null;
} else {
return getStatistics(reset);
}
}
/**
* Return the current statistics resetting the internal values if reset is true.
*/
public DTimeMetricStats getStatistics(boolean reset) {
if (reset) {
// Note these values are not guaranteed to be consistent wrt each other
// but should be reasonably consistent (small time between count and total)
final long maxVal = max.getThenReset();
final long totalVal = total.sumThenReset();
final long countVal = count.sumThenReset();
final long startTimeVal = startTime.getAndSet(System.currentTimeMillis());
return new DTimeMetricStats(name, startTimeVal, countVal, totalVal, maxVal);
} else {
return new DTimeMetricStats(name, startTime.get(), count.sum(), total.sum(), max.get());
}
}
/**
* Reset all the internal counters and start time.
*/
public void reset() {
startTime.set(System.currentTimeMillis());
max.reset();
count.reset();
total.reset();
}
/**
* Return the start time.
*/
public long getStartTime() {
return startTime.get();
}
/**
* Return the count of values.
*/
public long getCount() {
return count.sum();
}
/**
* Return the total of values.
*/
public long getTotal() {
return total.sum();
}
/**
* Return the max value.
*/
public long getMax() {
return max.get();
}
}
@@ -0,0 +1,30 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
class DTimedMetricMap implements TimedMetricMap {
private final String name;
private final ConcurrentHashMap<String, DTimedMetric> map = new ConcurrentHashMap<>();
DTimedMetricMap(String name) {
this.name = name;
}
@Override
public void add(String key, long exeMicros) {
map.computeIfAbsent(key, (k)-> new DTimedMetric(name + key)).add(exeMicros);
}
@Override
public void collect(boolean reset, List<MetaTimedMetric> list) {
for (DTimedMetric value : map.values()) {
value.collect(reset, list);
}
}
}
@@ -0,0 +1,49 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
import java.util.List;
/**
* Default profile location that uses stack trace.
*/
class DTimedProfileLocation extends DProfileLocation implements TimedProfileLocation {
private final String label;
private final TimedMetric timedMetric;
DTimedProfileLocation(int lineNumber, String label, TimedMetric timedMetric) {
super(lineNumber);
this.label = label;
this.timedMetric = timedMetric;
}
@Override
public String getLabel() {
return label;
}
@Override
public TimedMetric getMetric() {
return timedMetric;
}
@Override
public void add(long executionTime) {
timedMetric.add(executionTime);
}
@Override
public void collect(boolean reset, List<MetaTimedMetric> list) {
TimedMetricStats collect = timedMetric.collect(reset);
if (collect != null) {
collect.setLocation(obtain());
list.add(collect);
}
}
}
@@ -0,0 +1,28 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.metric.TimedMetric;
import java.util.List;
/**
* ProfileLocation that collects timing metrics.
*/
public interface TimedProfileLocation extends ProfileLocation {
/**
* Return the label.
*/
String getLabel();
/**
* Return the metric.
*/
TimedMetric getMetric();
/**
* Collect the metrics adding to the given list if the metrics are non empty.
*/
void collect(boolean reset, List<MetaTimedMetric> list);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.profile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Global registry of the TimedProfileLocation instances created.
*/
public class TimedProfileLocationRegistry {
private static final List<TimedProfileLocation> list = Collections.synchronizedList(new ArrayList<TimedProfileLocation>());
/**
* Register the timed profile location instance.
*/
public static void register(TimedProfileLocation location) {
list.add(location);
}
/**
* Return all the registered timed locations.
*/
public static List<TimedProfileLocation> registered() {
return list;
}
}
@@ -108,7 +108,7 @@ class CQueryBuilder {
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
return new CQueryUpdate(type, request, predicates, queryPlan.getSql());
return new CQueryUpdate(type, request, predicates, queryPlan);
}
predicates.prepare(true);
@@ -125,7 +125,7 @@ class CQueryBuilder {
// cache the query plan
queryPlan = new CQueryPlan(request, sql, sqlTree, false, false, predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryUpdate(type, request, predicates, sql);
return new CQueryUpdate(type, request, predicates, queryPlan);
}
private <T> String buildDeleteSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
@@ -251,8 +251,7 @@ class CQueryBuilder {
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
String sql = queryPlan.getSql();
return new CQueryRowCount(queryPlan, request, predicates, sql);
return new CQueryRowCount(queryPlan, request, predicates);
}
predicates.prepare(true);
@@ -283,7 +282,7 @@ class CQueryBuilder {
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryRowCount(queryPlan, request, predicates, sql);
return new CQueryRowCount(queryPlan, request, predicates);
}
/**
@@ -610,7 +609,7 @@ class CQueryBuilder {
if (!hasWhere) {
sb.append(" where ");
} else {
sb.append("and ");
sb.append(" and ");
}
for (int i = 0; i < softDeletePredicates.size(); i++) {
if (i > 0) {
@@ -55,7 +55,6 @@ public class CQueryEngine {
this.defaultFetchSizeFindEach = serverConfig.getJdbcFetchSizeFindEach();
this.defaultFetchSizeFindList = serverConfig.getJdbcFetchSizeFindList();
this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate();
this.historySupport = new CQueryHistorySupport(dbPlatform.getHistorySupport(), asOfTableMapping, serverConfig.getAsOfSysPeriod());
this.queryBuilder = new CQueryBuilder(dbPlatform, binder, historySupport, new CQueryDraftSupport(draftTableMap));
}
@@ -331,8 +330,6 @@ public class CQueryEngine {
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*
* @param request
*/
private <T> void prepareForPaging(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.OrmQueryRequest;
@@ -18,10 +19,12 @@ import java.util.List;
/**
* Base compiled query request for single attribute queries.
*/
class CQueryFetchSingleAttribute {
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
private final CQueryPlan queryPlan;
/**
* The overall find request wrapper object.
*/
@@ -56,17 +59,19 @@ class CQueryFetchSingleAttribute {
private final ScalarType<?> scalarType;
private long profileOffset;
/**
* Create the Sql select based on the request.
*/
CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan plan) {
CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan queryPlan) {
this.request = request;
this.queryPlan = queryPlan;
this.query = request.getQuery();
this.sql = plan.getSql();
this.sql = queryPlan.getSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
this.scalarType = plan.getSingleAttributeScalarType();
this.scalarType = queryPlan.getSingleAttributeScalarType();
query.setGeneratedSql(sql);
}
@@ -102,6 +107,9 @@ class CQueryFetchSingleAttribute {
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
request.slowQueryCheck(executionTimeMicros, rowCount);
queryPlan.executionTime(rowCount, executionTimeMicros, null);
getTransaction().profileEvent(this);
return result;
} finally {
@@ -109,6 +117,10 @@ class CQueryFetchSingleAttribute {
}
}
private SpiTransaction getTransaction() {
return request.getTransaction();
}
/**
* Return the bind log.
*/
@@ -125,7 +137,8 @@ class CQueryFetchSingleAttribute {
private void prepareExecute() throws SQLException {
SpiTransaction t = request.getTransaction();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
@@ -166,4 +179,10 @@ class CQueryFetchSingleAttribute {
}
}
@Override
public void profile() {
getTransaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId());
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebeaninternal.api.CQueryPlanKey;
@@ -48,6 +49,8 @@ public class CQueryPlan {
private final boolean autoTuned;
private final ProfileLocation profileLocation;
private final CQueryPlanKey planKey;
private final boolean rawSql;
@@ -87,6 +90,7 @@ public class CQueryPlan {
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.planKey = request.getQueryPlanKey();
this.profileLocation = request.getQuery().getProfileLocation();
this.autoTuned = request.getQuery().isAutoTuned();
this.asOfTableCount = request.getQuery().getAsOfTableCount();
this.sql = sqlRes.getSql();
@@ -107,6 +111,7 @@ public class CQueryPlan {
this.server = request.getServer();
this.dataTimeZone = server.getDataTimeZone();
this.beanType = request.getBeanDescriptor().getBeanType();
this.profileLocation = request.getQuery().getProfileLocation();
this.planKey = buildPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
this.autoTuned = false;
this.asOfTableCount = 0;
@@ -134,6 +139,10 @@ public class CQueryPlan {
return beanType;
}
public ProfileLocation getProfileLocation() {
return profileLocation;
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(dataTimeZone, rset);
}
@@ -252,4 +261,11 @@ public class CQueryPlan {
ScalarType<?> getSingleAttributeScalarType() {
return sqlTree.getRootNode().getSingleAttributeScalarType();
}
/**
* Return true if there are no statistics collected since the last reset.
*/
public boolean isEmptyStats() {
return stats.isEmpty();
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.ProfileLocation;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.meta.MetaQueryPlanOriginCount;
import io.ebean.meta.MetaQueryPlanStatistic;
@@ -43,6 +44,13 @@ public final class CQueryPlanStats {
this.origins = !collectQueryOrigins ? null : new ConcurrentHashMap<>();
}
/**
* Return true if there are no statistics collected since the last reset.
*/
public boolean isEmpty() {
return count.sum() == 0;
}
/**
* Add a query execution to the statistics.
*/
@@ -188,8 +196,9 @@ public final class CQueryPlanStats {
@Override
public String toString() {
return queryPlan + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans
+ " start:" + startTime + " lastQuery:" + lastQueryTime + " origins:" + origins;
ProfileLocation profileLocation = queryPlan.getProfileLocation();
String loc = (profileLocation == null) ? "" : profileLocation.shortDescription();
return "location:" + loc + " count:" + count + " time:" + totalTime + " maxTime:" + maxTime + " beans:" + totalBeans + " sql:" + getSql();
}
@Override
@@ -197,6 +206,11 @@ public final class CQueryPlanStats {
return queryPlan.getBeanType();
}
@Override
public ProfileLocation getProfileLocation() {
return queryPlan.getProfileLocation();
}
@Override
public long getExecutionCount() {
return count;
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.query;
import io.ebean.meta.MetaQueryPlanStatistic;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to collect query plan execution statistics.
*/
public class CQueryPlanStatsCollector {
private final boolean reset;
List<MetaQueryPlanStatistic> list = new ArrayList<>();
public CQueryPlanStatsCollector(boolean reset) {
this.reset = reset;
}
public boolean isReset() {
return reset;
}
public void add(MetaQueryPlanStatistic planStatistic) {
list.add(planStatistic);
}
public List<MetaQueryPlanStatistic> getList() {
return list;
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiQuery;
@@ -12,6 +11,7 @@ import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.util.BindParamsParser;
import org.slf4j.Logger;
@@ -318,15 +318,9 @@ public class CQueryPredicates {
}
private String parse(String expr, DeployParser deployParser) {
StringBuilder sb = new StringBuilder();
if (!isEmpty(expr)) {
if (sb.length() > 0) {
sb.append(" and ");
}
sb.append(deployParser.parse(expr));
}
return sb.toString();
if (expr == null) return "";
if (expr.isEmpty()) return expr;
return deployParser.parse(expr);
}
private String deriveHaving(DeployParser deployParser) {
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.OrmQueryRequest;
@@ -14,7 +15,7 @@ import java.sql.SQLException;
/**
* Executes the select row count query.
*/
class CQueryRowCount {
class CQueryRowCount implements SpiProfileTransactionEvent {
private final CQueryPlan queryPlan;
@@ -53,17 +54,19 @@ class CQueryRowCount {
private int rowCount;
private long profileOffset;
/**
* Create the Sql select based on the request.
*/
CQueryRowCount(CQueryPlan queryPlan, OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
CQueryRowCount(CQueryPlan queryPlan, OrmQueryRequest<?> request, CQueryPredicates predicates) {
this.queryPlan = queryPlan;
this.request = request;
this.query = request.getQuery();
this.sql = sql;
query.setGeneratedSql(sql);
this.sql = queryPlan.getSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
query.setGeneratedSql(sql);
}
/**
@@ -102,7 +105,8 @@ class CQueryRowCount {
long startNano = System.nanoTime();
try {
SpiTransaction t = request.getTransaction();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
@@ -121,6 +125,7 @@ class CQueryRowCount {
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode());
request.slowQueryCheck(executionTimeMicros, rowCount);
getTransaction().profileEvent(this);
return rowCount;
} finally {
@@ -128,6 +133,10 @@ class CQueryRowCount {
}
}
private SpiTransaction getTransaction() {
return request.getTransaction();
}
/**
* Close the resources.
*/
@@ -138,4 +147,10 @@ class CQueryRowCount {
pstmt = null;
}
@Override
public void profile() {
getTransaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId());
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.OrmQueryRequest;
@@ -12,7 +13,9 @@ import java.sql.SQLException;
/**
* Executes the delete query.
*/
class CQueryUpdate {
class CQueryUpdate implements SpiProfileTransactionEvent {
private final CQueryPlan queryPlan;
private final OrmQueryRequest<?> request;
@@ -43,17 +46,20 @@ class CQueryUpdate {
private int rowCount;
private long profileOffset;
/**
* Create the Sql select based on the request.
*/
CQueryUpdate(String type, OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
CQueryUpdate(String type, OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan queryPlan) {
this.type = type;
this.request = request;
this.queryPlan = queryPlan;
this.query = request.getQuery();
this.sql = sql;
query.setGeneratedSql(sql);
this.sql = queryPlan.getSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
query.setGeneratedSql(sql);
}
/**
@@ -90,7 +96,8 @@ class CQueryUpdate {
long startNano = System.nanoTime();
try {
SpiTransaction t = request.getTransaction();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
@@ -103,6 +110,8 @@ class CQueryUpdate {
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
request.slowQueryCheck(executionTimeMicros, rowCount);
queryPlan.executionTime(rowCount, executionTimeMicros, null);
getTransaction().profileEvent(this);
return rowCount;
} finally {
@@ -110,6 +119,10 @@ class CQueryUpdate {
}
}
private SpiTransaction getTransaction() {
return request.getTransaction();
}
/**
* Close the resources.
*/
@@ -118,4 +131,10 @@ class CQueryUpdate {
pstmt = null;
}
@Override
public void profile() {
getTransaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId());
}
}
@@ -151,7 +151,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
SpiQuery<T> query = request.getQuery();
if (query.isBeanCachePut()) {
if (request.isBeanCachePut()) {
// load the individual beans into the bean cache
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
Collection<T> c = result.getActualDetails();
@@ -160,7 +160,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
request.mergeCacheHits(result);
if (query.getUseQueryCache().isPut()) {
if (request.isQueryCachePut()) {
// load the query result into the query cache
result.setReadOnly(true);
request.putToQueryCache(result);
@@ -14,6 +14,7 @@ import io.ebean.OrderBy;
import io.ebean.OrderBy.Property;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.RawSql;
@@ -257,6 +258,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private short profileId;
private ProfileLocation profileLocation;
public DefaultOrmQuery(BeanDescriptor<T> desc, EbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.beanType = desc.getBeanType();
@@ -274,14 +277,20 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return beanDescriptor;
}
public boolean isFindAll() {
return whereExpressions == null && nativeSql == null && rawSql == null;
}
@Override
public void checkIdEqualTo() {
public boolean isFindById() {
if (id == null && whereExpressions != null) {
id = whereExpressions.idEqualTo(beanDescriptor.getIdName());
if (id != null) {
whereExpressions = null;
}
}
return id != null;
}
@Override
@@ -304,6 +313,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
@Override
public Query<T> setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
@Override
public boolean isAutoTunable() {
return nativeSql == null && beanDescriptor.isAutoTunable();
@@ -706,6 +721,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.m2mIncludeJoin = m2mIncludeJoin;
copy.profilingListener = profilingListener;
copy.profileLocation = profileLocation;
copy.rootTableAlias = rootTableAlias;
copy.distinct = distinct;
@@ -1732,4 +1748,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
public OrmUpdateProperties getUpdateProperties() {
return updateProperties;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.ExpressionList;
import io.ebean.ProfileLocation;
import io.ebean.UpdateQuery;
/**
@@ -41,6 +42,12 @@ public class DefaultUpdateQuery<T> implements UpdateQuery<T> {
return this;
}
@Override
public UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation) {
query.setProfileLocation(profileLocation);
return this;
}
@Override
public ExpressionList<T> where() {
return query.where();
@@ -19,7 +19,7 @@ public class AutoCommitTransactionManager extends TransactionManager {
* Create an autoCommit based Transaction.
*/
@Override
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
return new AutoCommitJdbcTransaction(prefix + id, explicit, c, this);
}
@@ -8,8 +8,13 @@ import io.ebeaninternal.api.SpiTransaction;
public class DefaultTransactionScopeManager extends TransactionScopeManager {
public DefaultTransactionScopeManager(TransactionManager transactionManager) {
super(transactionManager);
public DefaultTransactionScopeManager(String serverName) {
super(serverName);
}
@Override
public void register(TransactionManager manager) {
// do nothing
}
@Override
@@ -23,12 +28,12 @@ public class DefaultTransactionScopeManager extends TransactionScopeManager {
}
@Override
public SpiTransaction getScoped() {
public SpiTransaction getInScope() {
return DefaultTransactionThreadLocal.get(serverName);
}
@Override
public SpiTransaction get() {
public SpiTransaction getActive() {
SpiTransaction t = DefaultTransactionThreadLocal.get(serverName);
if (t == null || !t.isActive()) {
return null;
@@ -22,9 +22,9 @@ public class DocStoreTransactionManager extends TransactionManager {
}
@Override
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
long id = counter.incrementAndGet();
return createTransaction(profileId, explicit, null, id);
return createTransaction(explicit, null, id);
}
@Override
@@ -33,7 +33,7 @@ public class DocStoreTransactionManager extends TransactionManager {
}
@Override
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
return new DocStoreOnlyTransaction(prefix + id, explicit, this);
}
}
@@ -18,7 +18,7 @@ public class ExplicitTransactionManager extends TransactionManager {
* Create a ExplicitJdbcTransaction.
*/
@Override
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
return new ExplicitJdbcTransaction(prefix + id, explicit, c, this);
}
@@ -6,53 +6,26 @@ import io.ebeaninternal.api.SpiTransaction;
/**
* A TransactionScopeManager aware of external transaction managers.
*/
public class ExternalTransactionScopeManager extends TransactionScopeManager {
public class ExternalTransactionScopeManager extends DefaultTransactionScopeManager {
final ExternalTransactionManager externalManager;
private final ExternalTransactionManager externalManager;
/**
* Instantiates transaction scope manager.
*
* @param transactionManager the transaction manager
*/
public ExternalTransactionScopeManager(TransactionManager transactionManager, ExternalTransactionManager externalManager) {
super(transactionManager);
public ExternalTransactionScopeManager(String serverName, ExternalTransactionManager externalManager) {
super(serverName);
this.externalManager = externalManager;
}
@Override
public void commit() {
DefaultTransactionThreadLocal.commit(serverName);
}
@Override
public void end() {
DefaultTransactionThreadLocal.end(serverName);
public void register(TransactionManager manager) {
externalManager.setTransactionManager(manager);
}
@Override
public SpiTransaction getScoped() {
return get();
}
@Override
public SpiTransaction get() {
public SpiTransaction getActive() {
return (SpiTransaction) externalManager.getCurrentTransaction();
}
@Override
public void replace(SpiTransaction trans) {
DefaultTransactionThreadLocal.replace(serverName, trans);
}
@Override
public void rollback() {
DefaultTransactionThreadLocal.rollback(serverName);
}
@Override
public void set(SpiTransaction trans) {
DefaultTransactionThreadLocal.set(serverName, trans);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
@@ -40,6 +41,8 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private static final String notExpectedMessage = "Not expected on read only transaction";
private final TransactionManager manager;
/**
* The status of the transaction.
*/
@@ -60,23 +63,37 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private Map<String, Object> userObjects;
private long startNanos;
/**
* Create without a tenantId.
*/
ImplicitReadOnlyTransaction(Connection connection) {
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
this.manager = manager;
this.active = true;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
}
/**
* Create with a tenantId.
*/
ImplicitReadOnlyTransaction(Connection connection, Object tenantId) {
this(connection);
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection, Object tenantId) {
this(manager, connection);
this.tenantId = tenantId;
}
@Override
public void setLabel(String label) {
// do nothing
}
@Override
public String getLabel() {
return null;
}
@Override
public long profileOffset() {
return 0;
@@ -87,11 +104,26 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
// do nothing
}
@Override
public void setProfileStream(ProfileStream profileStream) {
// do nothing
}
@Override
public ProfileStream profileStream() {
return null;
}
@Override
public void setProfileLocation(ProfileLocation profileLocation) {
throw new IllegalStateException(notExpectedMessage);
}
@Override
public ProfileLocation getProfileLocation() {
return null;
}
@Override
public boolean isSkipCache() {
return false;
@@ -464,6 +496,8 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
}
connection = null;
active = false;
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
manager.collectMetricReadOnly(exeMicros);
}
/**
@@ -1,15 +1,16 @@
package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.annotation.PersistBatch;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -54,6 +55,11 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
protected final String id;
/**
* The user defined label to group execution statistics.
*/
protected String label;
/**
* Flag to indicate if this was an explicitly created Transaction.
*/
@@ -171,21 +177,17 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected DocStoreTransaction docStoreTxn;
private final ProfileStream profileStream;
private ProfileStream profileStream;
/**
* Create without ProfileStream option (no profiling).
*/
public JdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
this(null, id, explicit, connection, manager);
}
protected ProfileLocation profileLocation;
protected final long startNanos;
/**
* Create a new JdbcTransaction.
*/
public JdbcTransaction(ProfileStream profileStream, String id, boolean explicit, Connection connection, TransactionManager manager) {
public JdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
try {
this.profileStream = profileStream;
this.active = true;
this.id = id;
this.logPrefix = deriveLogPrefix(id);
@@ -193,6 +195,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.manager = manager;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
if (manager == null) {
this.skipCacheAfterWrite = true;
@@ -213,6 +216,16 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
}
@Override
public void setLabel(String label) {
this.label = label;
}
@Override
public String getLabel() {
return label;
}
@Override
public long profileOffset() {
return (profileStream == null) ? 0 : profileStream.offset();
@@ -225,11 +238,26 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
}
@Override
public void setProfileStream(ProfileStream profileStream) {
this.profileStream = profileStream;
}
@Override
public ProfileStream profileStream() {
return profileStream;
}
@Override
public void setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
/**
* Overridden in AutoCommitJdbcTransaction as that expects to run/operate with autocommit true.
*/
@@ -967,8 +995,17 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
private void profileEnd() {
if (profileStream != null) {
profileStream.end(manager);
if (manager != null) {
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
if (profileLocation != null) {
profileLocation.add(exeMicros);
} else if (label != null) {
manager.collectMetricNamed(exeMicros, label);
}
manager.collectMetric(exeMicros);
if (profileStream != null) {
profileStream.end(manager);
}
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
@@ -9,8 +10,10 @@ import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
@@ -24,6 +27,16 @@ class NoTransaction implements SpiTransaction {
static final NoTransaction INSTANCE = new NoTransaction();
@Override
public void setLabel(String label) {
// do nothing
}
@Override
public String getLabel() {
return null;
}
@Override
public boolean isActive() {
// always false
@@ -412,8 +425,23 @@ class NoTransaction implements SpiTransaction {
}
@Override
public void setProfileStream(ProfileStream profileStream) {
}
@Override
public ProfileStream profileStream() {
return null;
}
@Override
public void setProfileLocation(ProfileLocation profileLocation) {
}
@Override
public ProfileLocation getProfileLocation() {
return null;
}
}
@@ -34,7 +34,7 @@ abstract class TransactionFactory {
/**
* Return a new transaction.
*/
abstract SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel);
abstract SpiTransaction createTransaction(boolean explicit, int isolationLevel);
/**
* Set the Transaction Isolation level if required.
@@ -13,13 +13,10 @@ import java.sql.SQLException;
*/
class TransactionFactoryBasic extends TransactionFactory {
final DataSourceSupplier dataSourceSupplier;
private final DataSource dataSource;
TransactionFactoryBasic(TransactionManager manager, DataSourceSupplier dataSourceSupplier) {
super(manager);
this.dataSourceSupplier = dataSourceSupplier;
this.dataSource = dataSourceSupplier.getDataSource();
}
@@ -29,7 +26,7 @@ class TransactionFactoryBasic extends TransactionFactory {
Connection connection = null;
try {
connection = dataSource.getConnection();
return create(0, false, connection);
return create(false, connection);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
@@ -40,11 +37,11 @@ class TransactionFactoryBasic extends TransactionFactory {
}
@Override
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
Connection connection = null;
try {
connection = dataSource.getConnection();
SpiTransaction t = create(profileId, explicit, connection);
SpiTransaction t = create(explicit, connection);
return setIsolationLevel(t, explicit, isolationLevel);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
@@ -54,8 +51,8 @@ class TransactionFactoryBasic extends TransactionFactory {
}
}
private SpiTransaction create(int profileId, boolean explicit, Connection c) {
return manager.createTransaction(profileId, explicit, c, counter.incrementAndGet());
private SpiTransaction create(boolean explicit, Connection c) {
return manager.createTransaction(explicit, c, counter.incrementAndGet());
}
}
@@ -18,7 +18,6 @@ import java.sql.SQLException;
*/
class TransactionFactoryBasicWithRead extends TransactionFactoryBasic {
private final DataSource readOnlyDataSource;
TransactionFactoryBasicWithRead(TransactionManager manager, DataSourceSupplier dataSourceSupplier) {
@@ -32,7 +31,7 @@ class TransactionFactoryBasicWithRead extends TransactionFactoryBasic {
Connection connection = null;
try {
connection = readOnlyDataSource.getConnection();
return new ImplicitReadOnlyTransaction(connection);
return new ImplicitReadOnlyTransaction(manager, connection);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
@@ -25,17 +25,17 @@ class TransactionFactoryTenant extends TransactionFactory {
@Override
public SpiTransaction createQueryTransaction(Object tenantId) {
return create(0,false, tenantId);
return create(false, tenantId);
}
@Override
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
SpiTransaction t = create(profileId, explicit, null);
SpiTransaction t = create(explicit, null);
return setIsolationLevel(t, explicit, isolationLevel);
}
private SpiTransaction create(int profileId, boolean explicit, Object tenantId) {
private SpiTransaction create(boolean explicit, Object tenantId) {
Connection connection = null;
try {
if (tenantId == null) {
@@ -43,7 +43,7 @@ class TransactionFactoryTenant extends TransactionFactory {
tenantId = tenantProvider.currentId();
}
connection = dataSourceSupplier.getConnection(tenantId);
SpiTransaction transaction = manager.createTransaction(profileId, explicit, connection, counter.incrementAndGet());
SpiTransaction transaction = manager.createTransaction(explicit, connection, counter.incrementAndGet());
transaction.setTenantId(tenantId);
return transaction;
@@ -32,7 +32,7 @@ class TransactionFactoryTenantWithRead extends TransactionFactoryTenant {
tenantId = tenantProvider.currentId();
}
connection = dataSourceSupplier.getReadOnlyConnection(tenantId);
return new ImplicitReadOnlyTransaction(connection, tenantId);
return new ImplicitReadOnlyTransaction(manager, connection, tenantId);
} catch (PersistenceException ex) {
JdbcClose.close(connection);
@@ -1,20 +1,32 @@
package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.profile.TimedProfileLocation;
import io.ebeaninternal.server.profile.TimedProfileLocationRegistry;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.api.DocStoreUpdates;
@@ -25,6 +37,7 @@ import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -34,7 +47,7 @@ import java.util.Set;
* Keeps the Cache and Cluster in synch when transactions are committed.
* </p>
*/
public class TransactionManager {
public class TransactionManager implements SpiTransactionManager {
private static final Logger logger = LoggerFactory.getLogger(TransactionManager.class);
@@ -48,6 +61,12 @@ public class TransactionManager {
protected final BeanDescriptorManager beanDescriptorManager;
/**
* Ebean defaults this to true but for EJB compatible behaviour set this to
* false;
*/
private final boolean rollbackOnChecked;
/**
* Prefix for transaction id's (logging).
*/
@@ -113,6 +132,11 @@ public class TransactionManager {
private final SpiProfileHandler profileHandler;
private final TimedMetric txnMain;
private final TimedMetric txnReadOnly;
private final TimedMetricMap txnNamed;
private final TransactionScopeManager scopeManager;
/**
* Create the TransactionManager
*/
@@ -123,6 +147,7 @@ public class TransactionManager {
this.localL2Caching = options.localL2Caching;
this.persistBatch = options.config.getPersistBatch();
this.persistBatchOnCascade = options.config.appliedPersistBatchOnCascade();
this.rollbackOnChecked = options.config.isTransactionRollbackOnChecked();
this.beanDescriptorManager = options.descMgr;
this.viewInvalidation = options.descMgr.requiresViewEntityCacheInvalidation();
this.changeLogPrepare = options.descMgr.getChangeLogPrepare();
@@ -130,6 +155,7 @@ public class TransactionManager {
this.changeLogAsync = options.config.isChangeLogAsync();
this.clusterManager = options.clusterManager;
this.serverName = options.config.getName();
this.scopeManager = options.scopeManager;
this.backgroundExecutor = options.backgroundExecutor;
this.dataSourceSupplier = options.dataSourceSupplier;
this.docStoreActive = options.config.getDocStoreConfig().isActive();
@@ -142,6 +168,55 @@ public class TransactionManager {
CurrentTenantProvider tenantProvider = options.config.getCurrentTenantProvider();
this.transactionFactory = TransactionFactoryBuilder.build(this, dataSourceSupplier, tenantProvider);
MetricFactory metricFactory = MetricFactory.get();
this.txnMain = metricFactory.createTimedMetric("txn.main");
this.txnReadOnly = metricFactory.createTimedMetric("txn.readonly");
this.txnNamed = metricFactory.createTimedMetricMap("txn.named.");
scopeManager.register(this);
}
/**
* Create a new scoped transaction.
*/
private ScopedTransaction createScopedTransaction() {
return new ScopedTransaction(scopeManager);
}
/**
* Return the scope manager.
*/
public TransactionScopeManager scope() {
return scopeManager;
}
/**
* Set the transaction onto the scope.
*/
public void set(SpiTransaction txn) {
scopeManager.set(txn);
}
/**
* Return the current active transaction.
*/
public SpiTransaction getActive() {
return scopeManager.getActive();
}
/**
* Return the current active transaction as a scoped transaction.
*/
public ScopedTransaction getActiveScoped() {
return (ScopedTransaction) scopeManager.getActive();
}
/**
* Return the current transaction from thread local scope. Note that it may be inactive.
*/
public SpiTransaction getInScope() {
return scopeManager.getInScope();
}
/**
@@ -252,10 +327,13 @@ public class TransactionManager {
/**
* Create a new Transaction.
*/
public SpiTransaction createTransaction(int profileId, boolean explicit, int isolationLevel) {
return transactionFactory.createTransaction(profileId, explicit, isolationLevel);
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
return transactionFactory.createTransaction(explicit, isolationLevel);
}
/**
* Create a new Transaction for query only purposes (can use read only datasource).
*/
public SpiTransaction createQueryTransaction(Object tenantId) {
return transactionFactory.createQueryTransaction(tenantId);
}
@@ -263,10 +341,9 @@ public class TransactionManager {
/**
* Create a new transaction.
*/
protected SpiTransaction createTransaction(int profileId, boolean explicit, Connection c, long id) {
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
ProfileStream profileStream = profileHandler.createProfileStream(profileId);
return new JdbcTransaction(profileStream,prefix + id, explicit, c, this);
return new JdbcTransaction(prefix + id, explicit, c, this);
}
/**
@@ -342,14 +419,16 @@ public class TransactionManager {
}
}
/**
* Process a Transaction that comes from another framework or local code.
* <p>
* For cases where raw SQL/JDBC or other frameworks are used this can
* invalidate the appropriate parts of the cache.
* </p>
*/
public void externalModification(TransactionEventTable tableEvents) {
public void externalModification(TransactionEventTable tableEvent) {
SpiTransaction t = getActive();
if (t != null) {
t.getEvent().add(tableEvent);
} else {
externalModificationEvent(tableEvent);
}
}
private void externalModificationEvent(TransactionEventTable tableEvents) {
TransactionEvent event = new TransactionEvent();
event.add(tableEvents);
@@ -424,4 +503,185 @@ public class TransactionManager {
public void profileCollect(TransactionProfile transactionProfile) {
profileHandler.collectTransactionProfile(transactionProfile);
}
/**
* Collect execution time for an explicit transaction.
*/
public void collectMetric(long exeMicros) {
txnMain.add(exeMicros);
}
/**
* Collect execution time for implicit read only transaction.
*/
public void collectMetricReadOnly(long exeMicros) {
txnReadOnly.add(exeMicros);
}
/**
* Collect execution time for a named transaction.
*/
public void collectMetricNamed(long exeMicros, String label) {
txnNamed.add(label, exeMicros);
}
/**
* Collect the transaction execution statistics since the last reset.
*/
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
List<MetaTimedMetric> list = new ArrayList<>();
txnMain.collect(reset, list);
txnReadOnly.collect(reset, list);
for (TimedProfileLocation timedLocation : TimedProfileLocationRegistry.registered()) {
timedLocation.collect(reset, list);
}
txnNamed.collect(reset, list);
return list;
}
/**
* Begin an implicit transaction.
*/
public SpiTransaction beginServerTransaction() {
SpiTransaction t = createTransaction(false, -1);
scopeManager.set(t);
return t;
}
/**
* Exit a scoped transaction (that can be inactive - already committed etc).
*/
public void exitScopedTransaction(Object returnOrThrowable, int opCode) {
SpiTransaction st = getInScope();
if (st instanceof ScopedTransaction) {
// can be null for Supports as that can start as a 'No Transaction' and then
// effectively be replaced by transactions inside the scope
((ScopedTransaction)st).complete(returnOrThrowable, opCode);
}
}
@Override
public void externalRemoveTransaction() {
scopeManager.replace(null);
}
/**
* Push an externally created transaction into scope. This transaction is usually managed externally
* (e.g. Spring managed transaction).
*/
@Override
public ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope) {
ScopedTransaction scopedTxn = new ScopedTransaction(scopeManager);
scopedTxn.push(new ScopeTrans(rollbackOnChecked, false, transaction, txScope));
scopeManager.set(scopedTxn);
return scopedTxn;
}
/**
* Begin a scoped transaction.
*/
public ScopedTransaction beginScopedTransaction(TxScope txScope) {
txScope = initTxScope(txScope);
boolean setToScope = false;
ScopedTransaction txnContainer = getActiveScoped();
if (txnContainer == null) {
setToScope = true;
txnContainer = createScopedTransaction();
}
SpiTransaction transaction = txnContainer.current();
TxType type = txScope.getType();
boolean createTransaction = isCreateNewTransaction(transaction, type);
if (createTransaction) {
switch (type) {
case SUPPORTS:
case NOT_SUPPORTED:
case NEVER:
transaction = NoTransaction.INSTANCE;
break;
default:
transaction = createTransaction(true, txScope.getIsolationLevel());
initNewTransaction(transaction, txScope);
}
}
txnContainer.push(new ScopeTrans(rollbackOnChecked, createTransaction, transaction, txScope));
if (setToScope) {
set(txnContainer);
}
return txnContainer;
}
private void initNewTransaction(SpiTransaction transaction, TxScope txScope) {
if (txScope.isSkipCache()) {
transaction.setSkipCache(true);
}
String label = txScope.getLabel();
if (label != null) {
transaction.setLabel(label);
}
int profileId = txScope.getProfileId();
if (profileId > 0) {
transaction.setProfileStream(profileHandler.createProfileStream(profileId));
}
ProfileLocation profileLocation = txScope.getProfileLocation();
if (profileLocation != null) {
profileLocation.obtain();
transaction.setProfileLocation(profileLocation);
}
}
private TxScope initTxScope(TxScope txScope) {
if (txScope == null) {
return new TxScope();
} else {
// check for implied batch mode via setting batchSize
txScope.checkBatchMode();
return txScope;
}
}
/**
* Determine whether to create a new transaction or not.
* <p>
* This will also potentially throw exceptions for MANDATORY and NEVER types.
* </p>
*/
private boolean isCreateNewTransaction(SpiTransaction current, TxType type) {
switch (type) {
case REQUIRED:
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?");
}
}
}
@@ -21,13 +21,15 @@ public class TransactionManagerOptions {
final BeanDescriptorManager descMgr;
final DataSourceSupplier dataSourceSupplier;
final SpiProfileHandler profileHandler;
final TransactionScopeManager scopeManager;
public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler) {
public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler) {
this.localL2Caching = localL2Caching;
this.config = config;
this.scopeManager = scopeManager;
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.docStoreUpdateProcessor = docStoreUpdateProcessor;
@@ -8,24 +8,26 @@ import io.ebeaninternal.api.SpiTransactionScopeManager;
*/
public abstract class TransactionScopeManager implements SpiTransactionScopeManager {
protected final TransactionManager transactionManager;
protected final String serverName;
public TransactionScopeManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
this.serverName = transactionManager.getServerName();
public TransactionScopeManager(String serverName) {
this.serverName = serverName;
}
/**
* Return the current Transaction allowing it to be inactive.
* Register the transaction manager (for use by external transaction scopes).
*/
public abstract SpiTransaction getScoped();
public abstract void register(TransactionManager manager);
/**
* Return the current Transaction for this serverName and Thread.
* Return the current Transaction from internal Ebean scope.
*/
public abstract SpiTransaction get();
public abstract SpiTransaction getInScope();
/**
* Return the current Transaction potentially looking in external scope (like Spring).
*/
public abstract SpiTransaction getActive();
/**
* Set a new Transaction for this serverName and Thread.
@@ -271,6 +271,7 @@ public final class DefaultTypeManager implements TypeManager {
public void addEnumType(ScalarType<?> scalarType, Class<? extends Enum> enumClass) {
Set<Class<?>> mappedClasses = new HashSet<>();
mappedClasses.add(enumClass);
for (Object value : EnumSet.allOf(enumClass).toArray()) {
mappedClasses.add(value.getClass());
}
@@ -552,7 +553,7 @@ public final class DefaultTypeManager implements TypeManager {
* Return null if the EnumValue annotations are not present/used.
* </p>
*/
private ScalarType<?> createEnumScalarType2(Class<?> enumType) {
private ScalarTypeEnum<?> createEnumScalarType2(Class<?> enumType) {
boolean integerType = true;
@@ -589,8 +590,11 @@ public final class DefaultTypeManager implements TypeManager {
@Override
public ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType, EnumType type) {
ScalarType<?> scalarType = getScalarType(enumType);
if (scalarType != null) {
ScalarTypeEnum<?> scalarType = (ScalarTypeEnum<?>) getScalarType(enumType);
if (scalarType != null && !scalarType.isOverrideBy(type)) {
if (type != null && !scalarType.isCompatible(type)) {
throw new IllegalStateException("Error mapping Enum type:"+enumType+" It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)");
}
return scalarType;
}
@@ -603,7 +607,7 @@ public final class DefaultTypeManager implements TypeManager {
return scalarType;
}
private ScalarType<?> createEnumScalarTypePerSpec(Class<?> enumType, EnumType type) {
private ScalarTypeEnum<?> createEnumScalarTypePerSpec(Class<?> enumType, EnumType type) {
if (type == null) {
// default as per spec is ORDINAL
return new ScalarTypeEnumStandard.OrdinalEnum(enumType);
@@ -616,7 +620,7 @@ public final class DefaultTypeManager implements TypeManager {
}
}
private ScalarType<?> createEnumScalarTypePerExtentions(Class<? extends Enum<?>> enumType) {
private ScalarTypeEnum<?> createEnumScalarTypePerExtentions(Class<? extends Enum<?>> enumType) {
Method[] methods = enumType.getMethods();
for (Method method : methods) {
@@ -638,7 +642,7 @@ public final class DefaultTypeManager implements TypeManager {
* Return null if the EnumValue annotations are not present/used.
* </p>
*/
private ScalarType<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType) {
private ScalarTypeEnum<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType) {
Map<String, String> nameValueMap = new HashMap<>();
@@ -664,7 +668,7 @@ public final class DefaultTypeManager implements TypeManager {
* length create the ScalarType for the Enum.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private ScalarType<?> createEnumScalarType(Class enumType, Map<String, String> nameValueMap, boolean integerType, int dbColumnLength) {
private ScalarTypeEnum<?> createEnumScalarType(Class enumType, Map<String, String> nameValueMap, boolean integerType, int dbColumnLength) {
EnumToDbValueMap<?> beanDbMap = EnumToDbValueMap.create(integerType);
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import javax.persistence.EnumType;
import java.sql.SQLException;
import java.sql.Types;
import java.time.DayOfWeek;
@@ -22,6 +23,14 @@ public class ScalarTypeDayOfWeek extends ScalarTypeEnumWithMapping {
super(beanDbMap, DayOfWeek.class, 1);
}
/**
* We allow this to be overridden by a JPA EnumType.
*/
@Override
public boolean isOverrideBy(EnumType type) {
return type != null;
}
/**
* Bind DayOfWeek enum using getValue().
*/
@@ -1,15 +1,29 @@
package io.ebeaninternal.server.type;
import javax.persistence.EnumType;
import java.util.Set;
/**
* Marker interface for the Enum scalar types.
*/
public interface ScalarTypeEnum {
public interface ScalarTypeEnum<T> extends ScalarType<T> {
/**
* Return the IN values for DB constraint construction.
*/
Set<String> getDbCheckConstraintValues();
/**
* Return true if we allow this scalar enum type to be overridden.
* Ability to override the built-in support for java time DayOfWeek and Month.
*/
default boolean isOverrideBy(EnumType type) {
return false;
}
/**
* Return true if the scalar type is compatible with the specified enum type.
*/
boolean isCompatible(EnumType enumType);
}
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.TextException;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import javax.persistence.EnumType;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@@ -40,6 +41,11 @@ public class ScalarTypeEnumStandard {
this.length = maxValueLength(enumType);
}
@Override
public boolean isCompatible(EnumType enumType) {
return EnumType.STRING == enumType;
}
/**
* Return the IN values for DB constraint construction.
*/
@@ -128,6 +134,11 @@ public class ScalarTypeEnumStandard {
this.enumArray = EnumSet.allOf(enumType).toArray();
}
@Override
public boolean isCompatible(EnumType enumType) {
return EnumType.ORDINAL == enumType;
}
/**
* Return the IN values for DB constraint construction.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.type;
import javax.persistence.EnumType;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.LinkedHashSet;
@@ -24,6 +25,11 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i
this.length = length;
}
@Override
public boolean isCompatible(EnumType enumType) {
return enumType == null;
}
@Override
public long asVersion(Object value) {
throw new RuntimeException("not supported");

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