Compare commits

...
Author SHA1 Message Date
rbygrave 705e4f4098 [maven-release-plugin] prepare release ebean-parent-12.10.0 2021-07-22 19:03:43 +12:00
rbygrave 1642833fdb Improve javadoc on TQRootBean and generated javadoc on query bean forFetchGroup() method 2021-07-22 18:56:02 +12:00
rbygrave 492bb98e0e #2269 - findSingleAttributeList does not filter soft-deleted record 2021-07-22 17:12:06 +12:00
rbygrave a8456f90db Bump ebean-agent version to 2.10.0 2021-07-22 16:46:57 +12:00
Rob BygraveandGitHub 103b75172d Merge pull request #2267 from FOCONIS/pr/bugfix/connection_closed_on_clob
Retrieve (C|B)LOB on SqlRow construction
2021-07-22 16:44:05 +12:00
rbygrave 0baa3a0eac #2270 - ebean-agent enhancement NPE with MappedSuperclass with no properties that uses named database 2021-07-22 16:38:28 +12:00
rbygrave f26e4b5381 Bump jedis to 3.6.3 (from 3.6.1) 2021-07-22 12:37:42 +12:00
Jonas Pöhler 3d4de4dee5 FIX TestRawSqlService and TestRawSqlBuilder 2021-07-16 15:17:04 +02:00
Jonas Pöhler c621435a28 Merge remote-tracking branch 'ebean/master' into pr/bugfix/connection_closed_on_clob 2021-07-16 13:32:24 +02:00
rbygrave 7408483a43 #2264 - Use of default timezone for OffsetDateTime offsets problematic for unit tests and presentation layer 2021-07-15 17:14:22 +12:00
rbygrave 4d15535381 No functional change - tidy DefaultDbSqlContext 2021-07-13 16:48:45 +12:00
Rob BygraveandGitHub 787c2f90f2 Merge pull request #2188 from FOCONIS/fix-order-by
FIX: orderBy does not work when used on formula property
2021-07-13 16:41:33 +12:00
Rob BygraveandGitHub e590a2149d Merge pull request #2257 from FOCONIS/queries-are-cancelable
Queries are cancelable
2021-07-13 16:11:36 +12:00
rbygrave 8c77740aa7 Bump version tp 12.10.0-SNAPSHOT (with 12.9.4-RC1 ddl generator) 2021-07-13 15:48:10 +12:00
Rob BygraveandGitHub 29582b57f7 Merge pull request #2256 from FOCONIS/many-where-support
Improved @Where support, especially for Many-to-many tables
2021-07-13 15:01:49 +12:00
rbygrave 2763f333d4 #2262 - Tidy tests for Transaction postCommit Callback 2021-07-13 14:48:40 +12:00
rbygrave 8840628d34 #2262 - Transaction postCommit Callback not invoked when the active transaction has no database writes 2021-07-13 14:45:48 +12:00
rbygrave 6fb28d7ded #2255 Specific test for setParameter cannot assigning the value when parameter in selecting section of query 2021-07-13 12:54:21 +12:00
rbygrave be810aa568 [maven-release-plugin] prepare for next development iteration 2021-07-13 12:16:12 +12:00
rbygrave 8a698bd71e [maven-release-plugin] prepare release ebean-parent-12.9.3 2021-07-13 12:16:01 +12:00
rbygrave baa0594501 Change ebean-test tests to use H2 rather than mariadb 2021-07-13 12:10:23 +12:00
rbygrave e82b8c80f0 #2265 - ArrayIndexOutOfBoundsException: Index -1 out of bounds with @IdClass and batch insert accessing id property 2021-07-13 11:30:31 +12:00
Rob BygraveandGitHub f8e54be9a6 Merge pull request #2260 from casmir/master
Fix additional autotune runtimeExceptions due to toString() calls instead of asString()
2021-07-01 13:05:17 +12:00
Cas de Graaf d3137be984 in addition to #2236
- autotune still throws RunTimeException due to existing
 ORMQueryDetail.toString() calls, replaced with .asString()
2021-06-30 16:23:20 +02:00
Roland Praml 824cfa27be Queries are cancelable 2021-06-23 17:11:21 +02:00
Roland Praml 66cb6d5e7d Imporved @Where support, especially for Many-to-many tables 2021-06-21 14:50:15 +02:00
rbygrave 8f432aa0a9 #2251 - Transparent persistence of to-many associations (with orphanRemoval) 2021-06-18 16:05:25 +12:00
rbygrave 4aa4137abe Merge branch 'transparent_persistence' of https://github.com/cit-consulting/ebean into cit-consulting-transparent_persistence 2021-06-18 13:03:36 +12:00
rbygrave f4bb4e1b6a #2254 - OraclePlatform needs columnAliasPrefix 'c' - Regression in version 12.2.1 due to #1967 2021-06-18 12:37:16 +12:00
rbygrave 7af4e166a4 #2253 - Incorrect JSON format for LocalDateTime. Includes Z suffix when it should not. 2021-06-17 13:26:34 +12:00
Vladimir Konkov 1fa488d870 Test for transparent persistence of to-many associations 2021-06-14 16:20:25 +03:00
rbygrave 37a3a51b6b Bump test dependency for Postgres JDBC driver 2021-06-14 11:09:37 +12:00
rbygrave 6a6e8f906f Bump jedis dependency to 3.6.1 (from 3.6.0) 2021-06-14 11:09:13 +12:00
rbygrave 55bc498f9d No functional change - refactor QueryIterator to use Iterator.remove()
default method

Iterator.remove() has a default implementation that throws UnsupportedOperationException which is what we want. Remove the close() method from QueryIterator and implementations so that we just use the Iterator.remove() default implementation.
2021-06-11 10:20:47 +12:00
rbygrave 4ee784c68f [maven-release-plugin] prepare for next development iteration 2021-06-11 00:41:06 +12:00
rbygrave fc757c087a [maven-release-plugin] prepare release ebean-parent-12.9.2 2021-06-11 00:40:55 +12:00
rbygrave 884dd939b7 #2249 - Bug with DbJson Jackson property that is null is updated as null (unnecessarily included in update)
When the value is null and not changed then is was null before and should not be treated as dirty. Hence only check for dirty when value != null in BeanDescriptor 3182
2021-06-11 00:19:20 +12:00
Rob BygraveandGitHub 6fd2f556db Merge pull request #2248 from ebean-orm/feature/DbJson-DirtyDetection-Refactor
Refactor DbJson Jackson handling adding DatabaseConfig.setJsonDirtyByDefault()
2021-06-11 00:10:37 +12:00
rbygrave 4e088a40d9 Refactor DbJson Jackson handling adding DatabaseConfig.setJsonDirtyByDefault()
Adds the ability to change the default "jsonDirtyByDefault" configuration setting used with DbJson Jackson properties. Currently these default to being assumed dirty and this allows us to change that to be assumed not dirty (which is very likely the better default).
2021-06-11 00:08:03 +12:00
rbygrave 8ba66af878 Modify test TestQueryDefaultBatchSize to show use of ProfileLocation 2021-06-10 22:11:45 +12:00
rbygrave 441f51ed79 #2247 - Refactor internals - Change DefaultTypeManager - tidy 2021-06-10 20:30:07 +12:00
rbygrave 9ee891dc66 #2247 - Refactor internals - Change DefaultTypeManager from class.newInstance() to class.getDeclaredConstructor().newInstance() 2021-06-10 20:09:34 +12:00
rbygrave 87ffcbf23e No effective change - tidy CQuery 2021-06-10 19:45:27 +12:00
rbygrave adfc54200f No effective change - extract JacksonTypeHelper for ScalarTypeJsonObjectMapper 2021-06-10 19:27:42 +12:00
rbygrave 3c36b43449 No effective change - tidy EntityBeanIntercept 2021-06-10 19:26:56 +12:00
rbygrave 9be1dcff6c Tidy internals ScalarTypeJsonObjectMapper, no functional change 2021-06-10 19:00:35 +12:00
rbygrave 37dff62fa9 #2246 - ENH: Add DtoQuery findStream() and findIterate() 2021-06-10 14:55:31 +12:00
rbygrave 4fd462b1c8 #2244 - ENH: Add SQL Server support for transparent encryption via EncryptByPassPhrase, DecryptByPassPhrase 2021-06-10 13:34:33 +12:00
rbygrave 33ac1923ff No effective change - refactor move Str into server.util package
Allows the removal of io.ebeaninternal.server.lib package as that was the only thing in there.
2021-06-09 23:40:48 +12:00
rbygrave 0b87ae5eac No effective change - tidy BindParams 2021-06-09 23:38:10 +12:00
rbygrave 678743cc10 Use upper case E for class name of generated _Ebean$ModuleInfo 2021-06-09 21:59:19 +12:00
rbygrave c8345ce093 No functional change - refactor internal toStream() to use closure rather than explicit Runnable type for closing the stream
Conceptually javac closure might be better than an extra explicit type here
2021-06-09 16:21:15 +12:00
rbygrave f3af43a9ce Add support for findEach() on SqlQuery with RowMapper and scalar result 2021-06-09 15:49:48 +12:00
rbygrave 1df2ccb1c6 Refactor internals DefaultRelationalQueryEngine remove unnecessary method
- Remove findEach() method that took RowReader<T> and Consumer<T>
- Use findEachRow() instead
2021-06-09 15:19:35 +12:00
rbygrave 3add4472ef Refactor internals DefaultRelationalQueryEngine.findListMapper()
- Remove findListMapper() method
- Introduce RowReader interface
- Combine findList internals for both SqlRow and RowMapper use
2021-06-09 14:58:08 +12:00
rbygrave 4613f63c66 Refactor internals DataReader remove resetColumnPosition()
- Move the reset of position to the DataReader.next() method
- Tidy up use of wasNull() in RsetDataReader
2021-06-09 12:57:56 +12:00
rbygrave 612295fb9d [maven-release-plugin] prepare for next development iteration 2021-06-04 17:59:21 +12:00
Jonas Pöhler (JPo) 4977aa2ad1 Merge remote-tracking branch 'ebean/master' into pr/bugfix/connection_closed_on_clob 2021-04-26 14:54:32 +02:00
Jonas Pöhler (JPo) dd6a473008 ADD: Test and possible fix for (C)LOBs being handed out of connnection-context
Signed-off-by: Jonas Pöhler (JPo) <jonas.poehler@foconis.de>
2021-03-22 15:59:59 +01:00
Roland Praml ef1143bb70 FIX: orderBy does not work when used on formula property or in conjunction with "exists" query 2021-03-02 15:26:29 +01:00
154 changed files with 2741 additions and 1055 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.1</version>
<version>12.10.0</version>
</parent>
<name>ebean api</name>
@@ -0,0 +1,19 @@
package io.ebean;
/**
* Defines a cancelable query.
* <p>
* Typically holds a representation of the PreparedStatement to perform the
* actual cancel.
* </p>
*/
public interface CancelableQuery {
/**
* Cancel the query.
* <p>
* For JDBC this translates to calling cancel on the PreparedStatement.
* </p>
*/
void cancel();
}
+22 -1
View File
@@ -6,6 +6,7 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Query for performing native SQL queries that return DTO Bean's.
@@ -37,7 +38,7 @@ import java.util.function.Predicate;
*
* }</pre>
*/
public interface DtoQuery<T> {
public interface DtoQuery<T> extends CancelableQuery {
/**
* Execute the query returning a list.
@@ -45,6 +46,26 @@ public interface DtoQuery<T> {
@Nonnull
List<T> findList();
/**
* Execute the query iterating a row at a time.
* <p>
* Note that the QueryIterator holds resources related to the underlying
* resultSet and potentially connection and MUST be closed. We should use
* QueryIterator in a <em>try with resource block</em>.
*/
@Nonnull
QueryIterator<T> findIterate();
/**
* Execute the query returning a Stream.
* <p>
* Note that the Stream holds resources related to the underlying
* resultSet and potentially connection and MUST be closed. We should use
* the Stream in a <em>try with resource block</em>.
*/
@Nonnull
Stream<T> findStream();
/**
* Execute the query iterating a row at a time.
* <p>
+1 -10
View File
@@ -177,7 +177,7 @@ import java.util.stream.Stream;
*
* @param <T> the type of Entity bean this query will fetch.
*/
public interface Query<T> {
public interface Query<T> extends CancelableQuery {
/**
* The lock type (strength) to use with query FOR UPDATE row locking.
@@ -291,15 +291,6 @@ public interface Query<T> {
*/
UpdateQuery<T> asUpdate();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
* <p>
* This must be called from a different thread to the query executor.
* </p>
*/
void cancel();
/**
* Return a copy of the query.
* <p>
@@ -56,7 +56,7 @@ import java.util.Iterator;
*
* @param <T> the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
public interface QueryIterator<T> extends Iterator<T>, AutoCloseable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
@@ -70,12 +70,6 @@ public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
@Override
T next();
/**
* Remove is not allowed.
*/
@Override
void remove();
/**
* Close the underlying resources held by this iterator.
*/
@@ -37,7 +37,7 @@ import java.util.function.Predicate;
*
* }</pre>
*/
public interface SqlQuery extends Serializable {
public interface SqlQuery extends Serializable, CancelableQuery {
/**
* Execute the query returning a list.
@@ -365,5 +365,10 @@ public interface SqlQuery extends Serializable {
* Return the list of values.
*/
List<T> findList();
/**
* Find streaming the result effectively consuming a row at a time.
*/
void findEach(Consumer<T> consumer);
}
}
@@ -13,12 +13,10 @@ import java.util.Set;
* from the Map Set or List. The purpose of gathering the additions and removals
* is to support persisting ManyToMany objects. The additions and removals
* become inserts and deletes from the intersection table.
* </p>
* <p>
* Technically this is <em>NOT</em> an extension of
* <em>java.util.Collection</em>. The reason being that java.util.Map is not a
* Collection. I realise this makes this name confusing so I apologise for that.
* </p>
*/
public interface BeanCollection<E> extends Serializable {
@@ -68,7 +66,6 @@ public interface BeanCollection<E> extends Serializable {
* Return true if the collection is uninitialised or is empty without any held modifications.
* <p>
* Returning true means can safely skip cascade save for this bean collection.
* </p>
*/
boolean isSkipSave();
@@ -93,7 +90,6 @@ public interface BeanCollection<E> extends Serializable {
* <p>
* That is, if the collection was not loaded due to filterMany predicates etc
* then make sure the collection is set to empty.
* </p>
*/
boolean checkEmptyLazyLoad();
@@ -136,10 +132,7 @@ public interface BeanCollection<E> extends Serializable {
boolean isReadOnly();
/**
* Add the bean to the collection.
* <p>
* This is disallowed for BeanMap.
* </p>
* Add the bean to the collection. This is disallowed for BeanMap.
*/
void internalAdd(Object bean);
@@ -168,7 +161,6 @@ public interface BeanCollection<E> extends Serializable {
* Map.Entry.
* <p>
* For maps this returns the entrySet as we need the keys of the map.
* </p>
*/
Collection<?> getActualEntries();
@@ -185,6 +177,11 @@ public interface BeanCollection<E> extends Serializable {
*/
boolean isReference();
/**
* Return true if the collection is modify listening and has modifications.
*/
boolean hasModifications();
/**
* Set modify listening on or off. This is used to keep track of objects that
* have been added to or removed from the list set or map.
@@ -192,7 +189,6 @@ public interface BeanCollection<E> extends Serializable {
* This is required only for ManyToMany collections. The additions and
* deletions are used to insert or delete entries from the intersection table.
* Otherwise modifyListening is false.
* </p>
*/
void setModifyListening(ModifyListenMode modifyListenMode);
@@ -206,7 +202,6 @@ public interface BeanCollection<E> extends Serializable {
* <p>
* This will potentially end up as an insert into a intersection table for a
* ManyToMany.
* </p>
*/
void modifyAddition(E bean);
@@ -215,7 +210,6 @@ public interface BeanCollection<E> extends Serializable {
* <p>
* This will potentially end up as an delete from an intersection table for a
* ManyToMany.
* </p>
*/
void modifyRemoval(Object bean);
@@ -32,39 +32,47 @@ public final class EntityBeanIntercept implements Serializable {
private static final int STATE_REFERENCE = 1;
private static final int STATE_LOADED = 2;
/**
* Used when a bean is partially filled.
*/
private static final byte FLAG_LOADED_PROP = 1;
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
private transient final ReentrantLock lock = new ReentrantLock();
private transient NodeUsageCollector nodeUsageCollector;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
private transient PreGetterCallback preGetterCallback;
private String ebeanServerName;
private boolean deletedFromCollection;
/**
* The actual entity bean that 'owns' this intercept.
*/
private final EntityBean owner;
private EntityBean embeddedOwner;
private int embeddedOwnerIndex;
/**
* One of NEW, REF, UPD.
*/
private int state;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading - typically for SQL "report" type entity beans.
*/
@@ -74,37 +82,9 @@ public final class EntityBeanIntercept implements Serializable {
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
private boolean lazyLoadFailure;
/**
* Used when a bean is partially filled.
*/
private static final byte FLAG_LOADED_PROP = 1;
/**
* Set of changed properties.
*/
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
private final byte[] flags;
private boolean fullyLoadedBean;
private boolean loadedFromCache;
private final byte[] flags;
private Object[] origValues;
private Exception[] loadErrors;
private int lazyLoadProperty = -1;
@@ -983,7 +963,6 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Check for primitive boolean.
*/
@@ -1,13 +1,10 @@
package io.ebean.bean;
import java.util.List;
/**
* Holds entity beans by there type and id.
* <p>
* This is used to ensure only one instance for a given entity type and id is
* used to build object graphs from queries and lazy loading.
* </p>
*/
public interface PersistenceContext {
@@ -22,7 +19,6 @@ public interface PersistenceContext {
* <p>
* Returns an existing entity bean (if one is already there) and otherwise
* returns null.
* </p>
*/
Object putIfAbsent(Class<?> rootType, Object id, Object bean);
@@ -79,17 +75,11 @@ public interface PersistenceContext {
*/
boolean resetLimit();
/**
* Return the list of dirty beans held by this persistence context.
*/
List<Object> dirtyBeans();
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
* <p>
* If a bean has been deleted then for the same persistence context is should
* not be able to be fetched from persistence context or L2 cache.
* </p>
*/
class WithOption {
@@ -135,6 +135,11 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
// Support for modify additions deletions etc - ManyToMany
// ---------------------------------------------------------
@Override
public boolean hasModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@Override
public ModifyListenMode getModifyListening() {
return modifyListenMode;
@@ -145,7 +150,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
*/
@Override
public void setModifyListening(ModifyListenMode mode) {
this.modifyListenMode = mode;
this.modifyListening = mode != null && ModifyListenMode.NONE != mode;
if (modifyListening) {
@@ -193,6 +193,12 @@ public class DatabaseConfig {
*/
private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL;
/**
* When true then by default DbJson beans are assumed to be dirty.
* I believe we want to change this default to false in the future.
*/
private boolean jsonDirtyByDefault = true;
/**
* The database platform name. Used to imply a DatabasePlatform to use.
*/
@@ -737,6 +743,26 @@ public class DatabaseConfig {
this.jsonInclude = jsonInclude;
}
/**
* Return true if DbJson beans are assumed dirty by default.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
*/
public boolean isJsonDirtyByDefault() {
return jsonDirtyByDefault;
}
/**
* Set to false if we want DbJson beans to not be assumed to be dirty.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
*/
public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) {
this.jsonDirtyByDefault = jsonDirtyByDefault;
}
/**
* Return the name of the Database.
*/
@@ -2909,6 +2935,7 @@ public class DatabaseConfig {
jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude);
jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime);
jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate);
jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault);
runMigration = p.getBoolean("migration.run", runMigration);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
@@ -3369,7 +3396,7 @@ public class DatabaseConfig {
this.loadModuleInfo = loadModuleInfo;
}
public enum UuidVersion {
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
@@ -9,9 +9,6 @@ import java.sql.Types;
* functions for varchar, date and timestamp. If they are left null then that is
* treated as though that data type can not be encrypted in the DB and will
* instead use java client encryption.
* </p>
*
* @author rbygrave
*/
public abstract class AbstractDbEncrypt implements DbEncrypt {
@@ -47,13 +44,10 @@ public abstract class AbstractDbEncrypt implements DbEncrypt {
case Types.CHAR:
case Types.LONGVARCHAR:
return varcharEncryptFunction;
case Types.DATE:
return dateEncryptFunction;
case Types.TIMESTAMP:
return timestampEncryptFunction;
default:
return null;
}
@@ -5,8 +5,6 @@ import io.ebean.config.dbplatform.DbEncryptFunction;
/**
* H2 encryption support via encrypt decrypt function.
*
* @author rbygrave
*/
public class H2DbEncrypt extends AbstractDbEncrypt {
@@ -5,8 +5,6 @@ import io.ebean.config.dbplatform.DbEncryptFunction;
/**
* MySql aes_encrypt aes_decrypt based encryption support.
*
* @author rbygrave
*/
public class MySqlDbEncrypt extends AbstractDbEncrypt {
@@ -11,7 +11,6 @@ public class Oracle11Platform extends OraclePlatform {
public Oracle11Platform() {
super();
this.platform = Platform.ORACLE11;
this.columnAliasPrefix = "c";
this.sqlLimiter = new OracleRownumSqlLimiter();
this.basicSqlLimiter = new OracleRownumBasicLimiter();
dbIdentity.setIdType(IdType.SEQUENCE);
@@ -60,7 +60,6 @@ public class OracleDbEncrypt extends AbstractDbEncrypt {
* @param decryptFunction the decrypt stored procedure
*/
public OracleDbEncrypt(String encryptFunction, String decryptFunction) {
this.varcharEncryptFunction = new OraVarcharFunction(encryptFunction, decryptFunction);
this.dateEncryptFunction = new OraDateFunction(encryptFunction, decryptFunction);
}
@@ -22,6 +22,7 @@ public class OraclePlatform extends DatabasePlatform {
public OraclePlatform() {
super();
this.platform = Platform.ORACLE;
this.columnAliasPrefix = "c";
this.supportsDeleteTableAlias = true;
this.maxTableNameLength = 30;
this.maxConstraintNameLength = 30;
@@ -25,6 +25,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform {
this.platform = Platform.SQLSERVER;
// disable persistBatchOnCascade mode for
// SQL Server unless we are using sequences
this.dbEncrypt = new SqlServerDbEncrypt();
this.persistBatchOnCascade = PersistBatch.NONE;
this.idInExpandedForm = true;
this.selectCountWithAlias = true;
@@ -0,0 +1,46 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.config.dbplatform.AbstractDbEncrypt;
import io.ebean.config.dbplatform.DbEncryptFunction;
/**
* SQL Server EncryptByPassPhrase DecryptByPassPhrase based encryption support.
*/
public class SqlServerDbEncrypt extends AbstractDbEncrypt {
public SqlServerDbEncrypt() {
this.varcharEncryptFunction = new VarcharFunction();
this.dateEncryptFunction = new DateFunction();
}
@Override
public boolean isBindEncryptDataFirst() {
return false;
}
private static class VarcharFunction implements DbEncryptFunction {
@Override
public String getDecryptSql(String columnWithTableAlias) {
return "convert(nvarchar,DecryptByPassPhrase(?," + columnWithTableAlias + "))";
}
@Override
public String getEncryptBindSql() {
return "EncryptByPassPhrase(?,?)";
}
}
private static class DateFunction implements DbEncryptFunction {
@Override
public String getDecryptSql(String columnWithTableAlias) {
return "cast(convert(nvarchar,DecryptByPassPhrase(?," + columnWithTableAlias + ")) as date)";
}
@Override
public String getEncryptBindSql() {
return "EncryptByPassPhrase(?,format(?,'yyyy-MM-dd'))";
}
}
}
@@ -66,4 +66,17 @@ public class JdbcClose {
logger.warn("Error on connection rollback", e);
}
}
/**
* Cancels the statement
*/
public static void cancel(Statement stmt) {
try {
if (stmt != null) {
stmt.cancel();
}
} catch (SQLException e) {
logger.warn("Error on cancelling statement", e);
}
}
}
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.1</version>
<version>12.10.0</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -14,7 +14,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.1</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
@@ -113,7 +113,7 @@ public class AutoTuneDiffCollection {
diffCount++;
Origin origin = createOrigin(entry, point, tuneDetail.toString());
Origin origin = createOrigin(entry, point, tuneDetail.asString());
ProfileDiff diff = document.getProfileDiff();
if (diff == null) {
diff = new ProfileDiff();
@@ -147,7 +147,7 @@ public class AutoTuneDiffCollection {
Origin origin = new Origin();
origin.setKey(point.getKey());
origin.setBeanType(point.getBeanType());
origin.setDetail(entry.getDetail().toString());
origin.setDetail(entry.getDetail().asString());
origin.setCallStack(point.getCallOrigin().getFullDescription());
origin.setOriginal(query);
@@ -65,7 +65,7 @@ public class TunedQueryInfo implements Serializable {
@Override
public String toString() {
return tunedDetail.toString();
return tunedDetail.asString();
}
}
+17 -17
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.1</version>
<version>12.10.0</version>
</parent>
<name>ebean bom</name>
@@ -18,8 +18,8 @@
<ebean-migration.version>12.4.0</ebean-migration.version>
<ebean-test-docker.version>4.1</ebean-test-docker.version>
<ebean-datasource.version>7.0</ebean-datasource.version>
<ebean-agent.version>12.9.0</ebean-agent.version>
<ebean-maven-plugin.version>12.9.1</ebean-maven-plugin.version>
<ebean-agent.version>12.10.0</ebean-agent.version>
<ebean-maven-plugin.version>12.10.0</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.1</version>
<version>12.10.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
@@ -11,8 +11,6 @@ public interface DataReader {
boolean next() throws SQLException;
void resetColumnPosition();
void incrementPos(int increment);
byte[] getBinaryBytes() throws SQLException;
+8 -8
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.1</version>
<version>12.10.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,7 +15,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.1</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<profiles>
@@ -72,7 +72,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.0</version>
<version>12.9.4-RC1</version>
<scope>test</scope>
</dependency>
@@ -87,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
</dependency>
<dependency>
@@ -182,7 +182,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.10</version>
<version>42.2.20</version>
<optional>true</optional>
</dependency>
@@ -302,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<executions>
<execution>
<id>test</id>
@@ -155,7 +155,6 @@ public class BindParams implements Serializable {
* Set an In Out parameter using position.
*/
public void setParameter(int position, Object value, int outType) {
Param p = getParam(position);
p.setInValue(value);
p.setOutType(outType);
@@ -178,8 +177,8 @@ public class BindParams implements Serializable {
* Using position set the In value of a parameter. Note that for nulls you
* must use setNullParameter.
*/
@SuppressWarnings("rawtypes")
public void setParameter(int position, Object value) {
Param p = getParam(position);
if (value instanceof Collection) {
// use of postgres ANY with positioned parameter
@@ -214,7 +213,6 @@ public class BindParams implements Serializable {
* Set a named In Out parameter.
*/
public void setParameter(String name, Object value, int outType) {
Param p = getParam(name);
p.setInValue(value);
p.setOutType(outType);
@@ -232,7 +230,6 @@ public class BindParams implements Serializable {
* Set a named In parameter that is not null.
*/
public Param setParameter(String name, Object value) {
Param p = getParam(name);
p.setInValue(value);
return p;
@@ -299,7 +296,6 @@ public class BindParams implements Serializable {
* Return true if the bind hash and count has not changed.
*/
public boolean isSameBindHash() {
if (bindHash == null) {
bindHash = calcQueryPlanHash();
return false;
@@ -330,10 +326,6 @@ public class BindParams implements Serializable {
private final StringBuilder preparedSql;
public OrderedList() {
this(new ArrayList<>());
}
public OrderedList(List<Param> paramList) {
this.paramList = paramList;
this.preparedSql = new StringBuilder();
@@ -119,7 +119,7 @@ public class LoadManyRequest extends LoadRequest {
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
query.where().raw(extraWhere.replace("${ta}", "t0"));
query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_"));
}
query.setLazyLoadForParents(many);
@@ -0,0 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.bean.EntityBean;
/**
* SPI interface for underlying BeanDescriptor.
*/
public interface SpiBeanType {
/**
* Return true if the bean contains a many property that has modifications.
* <p>
* That is a ManyToMany or a OneToMany with orphan removal with additions
* or removals from the collection.
*/
boolean isToManyDirty(EntityBean bean);
}
@@ -0,0 +1,13 @@
package io.ebeaninternal.api;
/**
* Manager of SpiBeanTypes.
*/
public interface SpiBeanTypeManager {
/**
* Return the bean type for the given entity class.
*/
SpiBeanType getBeanType(Class<?> entityType);
}
@@ -0,0 +1,26 @@
package io.ebeaninternal.api;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
/**
* Cancellable query, that has a delegate.
*
* @author Roland Praml, FOCONIS AG
*
*/
public interface SpiCancelableQuery extends CancelableQuery {
/**
* Checks if the query was cancelled.
* @throws PersistenceException if query was cancelled.
*/
void checkCancelled();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
}
@@ -1,14 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.ExtendedServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.*;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.CallOrigin;
import io.ebean.config.DatabaseConfig;
@@ -25,6 +17,7 @@ import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Service Provider extension to EbeanServer.
@@ -234,6 +227,11 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find single attribute streaming the result to a consumer.
*/
<T> void findSingleAttributeEach(SpiSqlQuery query, Class<T> cls, Consumer<T> consumer);
/**
* SqlQuery find one with mapper.
*/
@@ -249,6 +247,16 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findIterate query.
*/
<T> QueryIterator<T> findDtoIterate(SpiDtoQuery<T> query);
/**
* DTO findStream query.
*/
<T> Stream<T> findDtoStream(SpiDtoQuery<T> query);
/**
* DTO findList query.
*/
@@ -0,0 +1,17 @@
package io.ebeaninternal.api;
import io.ebean.bean.PersistenceContext;
import java.util.List;
/**
* SPI extension to PersistenceContext.
*/
public interface SpiPersistenceContext extends PersistenceContext {
/**
* Return the list of dirty beans held by this persistence context.
*/
List<Object> dirtyBeans(SpiBeanTypeManager manager);
}
@@ -17,7 +17,6 @@ import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
@@ -31,7 +30,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes, SpiCancelableQuery {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -847,16 +846,6 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ReadEvent getFutureFetchAudit();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
/**
* Return true if this query has been cancelled.
*/
boolean isCancelled();
/**
* Return the base table to use if user defined on the query.
*/
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
/**
* SQL query binding (for SqlQuery and DtoQuery).
*/
public interface SpiSqlBinding {
public interface SpiSqlBinding extends SpiCancelableQuery {
/**
* Return the named or positioned parameters.
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.Transaction;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -196,7 +195,7 @@ public interface SpiTransaction extends Transaction {
* later. This is along the lines of 'extended persistence context'
* behaviour.
*/
PersistenceContext getPersistenceContext();
SpiPersistenceContext getPersistenceContext();
/**
* Set the persistence context to this transaction.
@@ -208,7 +207,7 @@ public interface SpiTransaction extends Transaction {
* and setPersistenceContext() enable a developer to reuse a single
* PersistenceContext with multiple transactions.
*/
void setPersistenceContext(PersistenceContext context);
void setPersistenceContext(SpiPersistenceContext context);
/**
* Return the underlying Connection for internal use.
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -379,12 +378,12 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return transaction.getPersistenceContext();
}
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
transaction.setPersistenceContext(context);
}
@@ -1,14 +1,10 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.CancelableQuery;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiSqlBinding;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.util.BindParamsParser;
@@ -17,11 +13,14 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
/**
* Wraps the objects involved in executing a SQL / Relational Query.
*/
public abstract class AbstractSqlQueryRequest {
public abstract class AbstractSqlQueryRequest implements CancelableQuery {
protected final SpiSqlBinding query;
@@ -41,6 +40,8 @@ public abstract class AbstractSqlQueryRequest {
protected long startNano;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the BeanFindRequest.
*/
@@ -48,6 +49,7 @@ public abstract class AbstractSqlQueryRequest {
this.server = server;
this.query = query;
this.transaction = (SpiTransaction) t;
this.query.setCancelableQuery(this);
}
/**
@@ -83,11 +85,6 @@ public abstract class AbstractSqlQueryRequest {
return transaction.isLogSql();
}
/**
* Set the resultSet and associated query plan if known.
*/
abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException;
/**
* Return the bindLog for this request.
*/
@@ -95,12 +92,15 @@ public abstract class AbstractSqlQueryRequest {
return bindLog;
}
/**
* Set the resultSet and associated query plan if known.
*/
abstract void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException;
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
return resultSet.next();
}
public abstract boolean next() throws SQLException;
protected abstract void requestComplete();
@@ -113,7 +113,6 @@ public abstract class AbstractSqlQueryRequest {
JdbcClose.close(pstmt);
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
@@ -145,24 +144,30 @@ public abstract class AbstractSqlQueryRequest {
}
protected void executeAsSql(Binder binder) throws SQLException {
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
lock.lock();
try {
query.checkCancelled();
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
} finally {
lock.unlock();
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
query.checkCancelled();
}
/**
@@ -172,4 +177,13 @@ public abstract class AbstractSqlQueryRequest {
return sql;
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1382,7 +1382,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Nonnull
@Override
public <T> FutureList<T> findFutureList(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
SpiQuery<T> spiQuery = (SpiQuery<T>) query.copy();
spiQuery.setFutureFetch(true);
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
@@ -1433,32 +1433,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Nonnull
@Override
public <T> Stream<T> findStream(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, transaction);
try {
request.initTransIfRequired();
return toStream(request.findIterate());
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
return toStream(findIterate(query, transaction));
}
private <T> Stream<T> toStream(QueryIterator<T> queryIterator) {
return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false)
.onClose(new QueryIteratorClose(queryIterator));
}
private static class QueryIteratorClose implements Runnable {
private final QueryIterator<?> iterator;
private QueryIteratorClose(QueryIterator<?> iterator) {
this.iterator = iterator;
}
@Override
public void run() {
iterator.close();
}
return stream(spliteratorUnknownSize(queryIterator, Spliterator.ORDERED), false).onClose(queryIterator::close);
}
@Override
@@ -1606,6 +1585,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
}
@Override
public <T> void findSingleAttributeEach(SpiSqlQuery query, Class<T> cls, Consumer<T> consumer) {
executeSqlQuery((req) -> req.findSingleAttributeEach(cls, consumer), query);
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
@@ -1649,6 +1633,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> QueryIterator<T> findDtoIterate(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return request.findIterate();
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
}
@Override
public <T> Stream<T> findDtoStream(SpiDtoQuery<T> query) {
return toStream(findDtoIterate(query));
}
@Override
public <T> List<T> findDtoList(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.QueryIterator;
import io.ebean.core.type.DataReader;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -52,6 +53,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
ormQuery.setType(type);
ormQuery.setManualId();
query.setCancelableQuery(ormQuery);
// execute the underlying ORM query returning the ResultSet
SpiResultSet result = server.findResultSet(ormQuery, transaction);
this.pstmt = result.getStatement();
@@ -90,6 +92,11 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
}
}
public QueryIterator<T> findIterate() {
flushJdbcBatchOnQuery();
return queryEngine.findIterate(this);
}
public void findEach(Consumer<T> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
@@ -110,9 +117,13 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
return queryEngine.findList(this);
}
public boolean next() throws SQLException {
query.checkCancelled();
return dataReader.next();
}
@SuppressWarnings("unchecked")
public T readNextBean() throws SQLException {
dataReader.resetColumnPosition();
return (T) plan.readRow(dataReader);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.CancelableQuery;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
@@ -35,7 +36,6 @@ import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -379,9 +379,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void setBatched() {
batched = true;
if (type == Type.INSERT || type == Type.UPDATE) {
// used to trigger automatic jdbc batch flush
intercept.registerGetterCallback(this);
getterCallback = true;
if (beanDescriptor.hasSingleIdProperty()) {
// used to trigger automatic jdbc batch flush
intercept.registerGetterCallback(this);
getterCallback = true;
}
}
}
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.persist.TrimLogSql;
@@ -20,17 +20,22 @@ public interface RelationalQueryEngine {
/**
* Find a list of beans using relational query.
*/
List<SqlRow> findList(RelationalQueryRequest request);
/**
* Find each query using relational query.
*/
void findEach(RelationalQueryRequest request, Consumer<SqlRow> consumer);
<T> List<T> findList(RelationalQueryRequest request, RowReader<T> reader);
/**
* Find each while query using relational query.
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
<T> void findEach(RelationalQueryRequest request, RowReader<T> reader, Predicate<T> consumer);
/**
* Find each via raw consumer.
*/
void findEach(RelationalQueryRequest request, RowConsumer mapper);
/**
* Find one via mapper.
*/
<T> T findOne(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find single attribute.
@@ -43,19 +48,9 @@ public interface RelationalQueryEngine {
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
/**
* Find one via mapper.
* Find single attribute streaming the result to a consumer.
*/
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find list via mapper.
*/
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find each via raw consumer.
*/
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
<T> void findSingleAttributeEach(RelationalQueryRequest request, Class<T> cls, Consumer<T> consumer);
/**
* Collect SQL query execution statistics.
@@ -1,10 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
import io.ebean.*;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlBinding;
@@ -57,18 +53,24 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
boolean findEachRow(RowConsumer mapper) {
flushJdbcBatchOnQuery();
queryEngine.findEachRow(this, mapper);
queryEngine.findEach(this, mapper);
return true;
}
<T> List<T> findListMapper(RowMapper<T> mapper) {
flushJdbcBatchOnQuery();
return queryEngine.findListMapper(this, mapper);
return queryEngine.findList(this, () -> mapper.map(resultSet, rows++));
}
<T> T findOneMapper(RowMapper<T> mapper) {
flushJdbcBatchOnQuery();
return queryEngine.findOneMapper(this, mapper);
return queryEngine.findOne(this, mapper);
}
public <T> boolean findSingleAttributeEach(Class<T> cls, Consumer<T> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findSingleAttributeEach(this, cls, consumer);
return true;
}
public <T> List<T> findSingleAttributeList(Class<T> cls) {
@@ -83,17 +85,17 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
public void findEach(Consumer<SqlRow> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
queryEngine.findEach(this, (resultSet, rowNum) -> consumer.accept(createNewRow()));
}
public void findEachWhile(Predicate<SqlRow> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, consumer);
queryEngine.findEach(this, this::createNewRow, consumer);
}
public List<SqlRow> findList() {
flushJdbcBatchOnQuery();
return queryEngine.findList(this);
return queryEngine.findList(this, this::createNewRow);
}
/**
@@ -135,16 +137,15 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
return resultSet;
}
public void incrementRows() {
rows++;
}
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
List<T> list = new ArrayList<>();
while (next()) {
list.add(mapper.map(resultSet, rows++));
@Override
public boolean next() throws SQLException {
query.checkCancelled();
if (!resultSet.next()) {
return false;
} else {
rows++;
return true;
}
return list;
}
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.core;
import java.sql.SQLException;
/**
* Read a row building a result for that row.
*/
public interface RowReader<T> {
/**
* Build and return a result for a row.
*/
T read() throws SQLException;
}
@@ -38,15 +38,7 @@ import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
import io.ebean.plugin.Property;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.LoadBeanContext;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdatePlan;
import io.ebeaninternal.api.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
@@ -119,7 +111,7 @@ import static io.ebeaninternal.server.persist.DmlUtil.isNullOrZero;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -1755,6 +1747,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return idProperty != null;
}
/**
* Return false for IdClass case with multiple @Id properties.
*/
public boolean hasSingleIdProperty() {
return idPropertyIndex != -1;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
@@ -1880,7 +1879,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
if (refBean == null) {
refBean = createReference(readOnly, false, id, pc);
}
return (EntityBean)refBean;
return (EntityBean) refBean;
}
/**
@@ -2017,6 +2016,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return owner.getBeanDescriptor(otherType);
}
/**
* Returns true, if the table is managed (i.e. an existing m2m relation).
*/
public boolean isTableManaged(String tableName) {
return owner.isTableManaged(tableName);
}
/**
* Return the order column property.
*/
@@ -2937,6 +2943,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return false;
}
@Override
public boolean isToManyDirty(EntityBean bean) {
final EntityBeanIntercept ebi = bean._ebean_getIntercept();
for (BeanPropertyAssocMany<?> many : propertiesManySave) {
if (ebi.isLoadedProperty(many.getPropertyIndex())) {
final BeanCollection<?> value = (BeanCollection<?>) many.getValue(bean);
if (value != null && value.hasModifications()) {
return true;
}
}
}
return false;
}
/**
* Return true if the bean is draftable and considered a 'live' instance.
*/
@@ -3139,7 +3159,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
public boolean isIdLoaded(EntityBeanIntercept ebi) {
return ebi.isLoadedProperty(idPropertyIndex);
// assume id loaded for IdClass case with idPropertyIndex == -1
return idPropertyIndex == -1 ? true : ebi.isLoadedProperty(idPropertyIndex);
}
boolean hasIdValue(EntityBean bean) {
@@ -3179,7 +3200,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
int propertyIndex = beanProperty.getPropertyIndex();
if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) {
Object value = beanProperty.getValue(ebi.getOwner());
if (value == null || beanProperty.isDirtyValue(value)) {
if (value != null && beanProperty.isDirtyValue(value)) {
// mutable scalar value which is considered dirty so mark
// it as such so that it is included in an update
ebi.markPropertyAsChanged(propertyIndex);
@@ -26,9 +26,7 @@ import io.ebean.meta.MetricVisitor;
import io.ebean.meta.QueryPlanInit;
import io.ebean.plugin.BeanType;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.core.InternString;
@@ -88,7 +86,7 @@ import java.util.concurrent.TimeUnit;
/**
* Creates BeanDescriptors.
*/
public class BeanDescriptorManager implements BeanDescriptorMap {
public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeManager {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
@@ -263,6 +261,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return descQueueMap.get(queueId);
}
@Override
public SpiBeanType getBeanType(Class<?> entityType) {
return getBeanDescriptor(entityType);
}
@Override
@SuppressWarnings("unchecked")
public <T> BeanDescriptor<T> getBeanDescriptor(Class<T> entityType) {
@@ -442,6 +445,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return tableToDescMap.get(tableName.toLowerCase());
}
@Override
public boolean isTableManaged(String tableName) {
return tableToDescMap.get(tableName.toLowerCase()) != null
|| tableToViewDescMap.get(tableName.toLowerCase()) != null;
}
/**
* Invalidate entity beans based on views via their dependent tables.
*/
@@ -76,4 +76,10 @@ public interface BeanDescriptorMap {
* Return true if Jackson core is present on the classpath.
*/
boolean isJacksonCorePresent();
/**
* Returns true, if the given table (or view) is managed by ebean
* (= an entity exists)
*/
boolean isTableManaged(String tableName);
}
@@ -439,6 +439,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
boolean softDelete = targetDescriptor.isSoftDelete();
boolean needsX2Table = softDelete || getExtraWhere() != null;
StringBuilder sb = new StringBuilder(50);
SpiQuery<?> query = request.getQueryRequest().getQuery();
if (hasJoinTable()) {
@@ -446,7 +447,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
} else {
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
}
if (softDelete && hasJoinTable()) {
if (needsX2Table && hasJoinTable()) {
sb.append(" x join ");
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
sb.append(" x2 on ");
@@ -461,6 +462,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
exportedProperties[i].appendWhere(sb, "x.", path);
}
if (getExtraWhere() != null) {
sb.append(" and ");
if (hasJoinTable()) {
sb.append(getExtraWhere().replace("${ta}", "x2").replace("${mta}", "x"));
} else {
sb.append(getExtraWhere().replace("${ta}", "x"));
}
}
if (softDelete) {
String alias = hasJoinTable() ? "x2" : "x";
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias));
@@ -1061,4 +1072,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
/**
* Returns true, if we must create a m2m join table.
*/
public boolean createJoinTable() {
if (hasJoinTable() && getMappedBy() == null) {
// only create on other 'owning' side
return !descriptor.isTableManaged(intersectionJoin.getTable());
} else {
return false;
}
}
}
@@ -10,7 +10,7 @@ public interface DbSqlContext {
/**
* Add a join to the sql query.
*/
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2);
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere);
/**
* Push the current table alias onto the stack.
@@ -34,6 +34,8 @@ public final class TableJoin {
private final int queryHash;
private final PropertyForeignKey foreignKey;
private final String extraWhere;
public TableJoin(DeployTableJoin deploy) {
this(deploy, null);
@@ -44,6 +46,7 @@ public final class TableJoin {
*/
public TableJoin(DeployTableJoin deploy, PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
this.extraWhere = deploy.getExtraWhere();
this.table = InternString.intern(deploy.getTable());
this.type = deploy.getType();
this.inheritInfo = deploy.getInheritInfo();
@@ -57,6 +60,7 @@ public final class TableJoin {
private TableJoin(TableJoin source, String overrideColumn) {
this.foreignKey = null;
this.extraWhere = source.extraWhere;
this.table = source.table;
this.type = source.type;
this.inheritInfo = source.inheritInfo;
@@ -146,7 +150,7 @@ public final class TableJoin {
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
String joinLiteral = joinType.getLiteral(type);
ctx.addJoin(joinLiteral, table, columns(), a1, a2);
ctx.addJoin(joinLiteral, table, columns(), a1, a2, extraWhere);
return joinType.autoToOuter(type);
}
@@ -86,6 +86,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* collection.
*/
public void setExtraWhere(String extraWhere) {
this.tableJoin.setExtraWhere(extraWhere);
this.extraWhere = extraWhere;
}
@@ -33,6 +33,8 @@ public class DeployTableJoin {
private ArrayList<DeployTableJoinColumn> columns = new ArrayList<>(4);
private InheritInfo inheritInfo;
private String extraWhere;
/**
* Create a DeployTableJoin.
@@ -137,6 +139,18 @@ public class DeployTableJoin {
this.type = type;
}
/**
* Returns the clause of an extra &#64;Where annotation.
* @return
*/
public String getExtraWhere() {
return extraWhere;
}
public void setExtraWhere(String extraWhere) {
this.extraWhere = extraWhere;
}
public DeployTableJoin createInverse(String tableName) {
DeployTableJoin inverse = new DeployTableJoin();
@@ -118,7 +118,7 @@ class AnnotationAssocManys extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
FetchPreference fetchPreference = get(prop, FetchPreference.class);
@@ -96,7 +96,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
// not expecting this to be used on assoc one properties
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class);
@@ -153,7 +153,7 @@ public class AnnotationFields extends AnnotationParser {
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
initWhoProperties(prop);
@@ -334,7 +334,7 @@ public class AnnotationFields extends AnnotationParser {
}
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class);
@@ -129,4 +129,11 @@ public abstract class AnnotationParser extends AnnotationBase {
}
return columnNames;
}
/**
* Process any formula from &#64;Formula or &#64;Where.
*/
protected String processFormula(String source) {
return source == null ? null : source.replace("${dbTableName}", descriptor.getBaseTable());
}
}
@@ -1,13 +0,0 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>AvajeLib</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Core services for an application.
<P>
Core services including Logging, Deployment properties and Background thread for
running frequent tasks.
</P>
</Body>
</HTML>
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.persist.dml;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.BatchedPstmt;
import io.ebeaninternal.server.persist.BatchedPstmtHolder;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -1,14 +1,9 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.bean.NodeUsageListener;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebean.core.type.DataReader;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.util.JdbcClose;
@@ -19,12 +14,7 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanCollectionHelpFactory;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.deploy.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,11 +24,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -47,11 +33,9 @@ import java.util.concurrent.locks.ReentrantLock;
* The SqlSelect is based on a tree (Object Graph). The tree is traversed to see
* what parts are included in the tree according to the value of
* find.getInclude();
* </p>
* <p>
* The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is
* the key object used in reading the flat resultSet back into Objects.
* </p>
*/
public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTransactionEvent {
@@ -156,8 +140,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
*/
private PreparedStatement pstmt;
private boolean cancelled;
private String bindLog;
private final CQueryPlan queryPlan;
@@ -205,19 +187,15 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
this.query = request.getQuery();
this.queryMode = query.getMode();
this.lazyLoadManyProperty = query.getLazyLoadMany();
this.readOnly = request.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.getParentNode();
this.profilingListener = query.getProfilingListener();
this.autoTuneProfiling = profilingListener != null;
this.profilingListenerRef = autoTuneProfiling ? new WeakReference<>(profilingListener) : null;
// set the generated sql back to the query
// so its available to the user...
query.setGeneratedSql(queryPlan.getSql());
SqlTree sqlTree = queryPlan.getSqlTree();
this.rootNode = sqlTree.getRootNode();
this.manyProperty = sqlTree.getManyProperty();
@@ -293,15 +271,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
public void cancel() {
lock.lock();
try {
this.cancelled = true;
if (pstmt != null) {
try {
logger.debug("Cancelling query");
pstmt.cancel();
} catch (SQLException e) {
throw new PersistenceException("Error cancelling query", e);
}
}
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
@@ -333,9 +303,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
ResultSet prepareResultSet(boolean forwardOnlyHint) throws SQLException {
lock.lock();
try {
if (cancelled) {
throw new SQLException("Query cancelled");
}
// cancelled before we started
query.checkCancelled();
startNano = System.nanoTime();
SpiTransaction t = request.getTransaction();
profileOffset = t.profileOffset();
@@ -363,17 +332,18 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
bindLog = predicates.bind(queryPlan.bindEncryptedProperties(pstmt, conn));
return pstmt.executeQuery();
} finally {
lock.unlock();
}
ResultSet ret = pstmt.executeQuery();
query.checkCancelled();
return ret;
}
/**
* Close the resources.
* <p>
* The JDBC resultSet and statement need to be closed. Its important that this method is called.
* </p>
*/
public void close() {
try {
@@ -405,14 +375,12 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
@Override
public void setLazyLoadedChildBean(EntityBean bean, Object lazyLoadParentId) {
if (lazyLoadParentId != null) {
if (!lazyLoadParentId.equals(this.lazyLoadParentId)) {
// get the appropriate parent bean from the persistence context
this.lazyLoadParentBean = (EntityBean) lazyLoadManyProperty.getBeanDescriptor().contextGet(getPersistenceContext(), lazyLoadParentId);
this.lazyLoadParentId = lazyLoadParentId;
}
// add the loadedBean to the appropriate collection of lazyLoadParentBean
lazyLoadManyProperty.addBeanToCollectionWithCreate(lazyLoadParentBean, bean, true);
}
@@ -423,10 +391,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* <p>
* If the query includes a many then the first object in the returned array is
* the one/master and the second the many/detail.
* </p>
*/
private boolean readNextBean() throws SQLException {
if (!moveToNextRow()) {
if (currentBean == null) {
nextBean = null;
@@ -440,7 +406,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
loadedBeanCount++;
if (manyProperty == null) {
// only single resultSet row required to build object so we are done
// read a single resultSet row into single bean
@@ -486,14 +451,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Return true if we can move to the next resultSet row.
*/
private boolean moveToNextRow() throws SQLException {
if (!dataReader.next()) {
noMoreRows = true;
return false;
}
rowCount++;
dataReader.resetColumnPosition();
return true;
}
@@ -502,7 +464,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
boolean readBean() throws SQLException {
boolean result = hasNext();
updateExecutionStatistics();
return result;
@@ -522,7 +483,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
boolean hasNext() throws SQLException {
lock.lock();
try {
if (noMoreRows || cancelled) {
query.checkCancelled();
if (noMoreRows) {
return false;
}
if (hasNextCache) {
@@ -539,20 +501,16 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Read version beans and their effective dates.
*/
List<Version<T>> readVersions() throws SQLException {
List<Version<T>> versionList = new ArrayList<>();
Version<T> version;
while ((version = readNextVersion()) != null) {
versionList.add(version);
}
updateExecutionStatistics();
return versionList;
}
private Version<T> readNextVersion() throws SQLException {
if (moveToNextRow()) {
return rootNode.loadVersion(this);
}
@@ -696,7 +654,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Should we create profileNodes for beans created in this query.
* <p>
* This is true for all queries except lazy load bean queries.
* </p>
*/
@Override
public boolean isAutoTuneProfiling() {
@@ -707,13 +664,11 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
private String getPath(String propertyName) {
if (currentPrefix == null) {
return propertyName;
} else if (propertyName == null) {
return currentPrefix;
}
String path = currentPathMap.get(propertyName);
if (path != null) {
return path;
@@ -724,7 +679,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
@Override
public void profileBean(EntityBeanIntercept ebi, String prefix) {
ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix);
ebi.setNodeUsageCollector(new NodeUsageCollector(node, profilingListenerRef));
}
@@ -749,7 +703,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* a find many query with read auditing so build the ReadEvent and log it.
*/
void auditFindMany() {
if (auditIds != null && !auditIds.isEmpty()) {
// get the id values of the underlying collection
ReadEvent futureReadEvent = query.getFutureFetchAudit();
@@ -787,7 +740,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Add the id to the audit id buffer and flush if needed in batches of 100.
*/
private void auditNextBean() {
if (auditIds == null) {
auditIds = new ArrayList<>(100);
}
@@ -189,6 +189,12 @@ class CQueryBuilder {
SpiQuery<?> query = request.getQuery();
query.setSingleAttribute();
if (!query.isIncludeSoftDeletes()) {
BeanDescriptor<?> desc = request.getBeanDescriptor();
if (desc.isSoftDelete()) {
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias())));
}
}
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
@@ -616,11 +622,19 @@ class CQueryBuilder {
if (request.isInlineCountDistinct()) {
sb.append(")");
}
if (distinct && dbOrderBy != null && !query.isSingleAttribute()) {
if (distinct && dbOrderBy != null) {
// add the orderBy columns to the select clause (due to distinct)
final OrderBy<?> orderBy = query.getOrderBy();
if (orderBy != null && orderBy.supportsSelect()) {
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
String trimmed = DbOrderByTrim.trim(dbOrderBy);
if (query.isSingleAttribute() && trimmed.equals(select.getSelectSql())) {
// NOP, already in SQL
// TODO: what to do if we select("id").orderBy("prop,id")?
// Can we live with a query like "select t0.id, t0.prop, t0.id from"
// or should we elliminate the second "t0.id" from select
} else {
sb.append(", ").append(trimmed);
}
}
}
}
@@ -16,7 +16,7 @@ import io.ebeaninternal.server.core.DiffHelp;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.persist.Binder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -66,11 +66,13 @@ public class CQueryEngine {
public <T> int delete(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(true, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
public <T> int update(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(false, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
@@ -97,6 +99,7 @@ public class CQueryEngine {
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -151,6 +154,7 @@ public class CQueryEngine {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -164,6 +168,7 @@ public class CQueryEngine {
public <T> int findCount(OrmQueryRequest<T> request) {
CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request);
request.setCancelableQuery(rcQuery);
try {
int count = rcQuery.findCount();
@@ -235,8 +240,10 @@ public class CQueryEngine {
} catch (SQLException e) {
try {
PersistenceException pex = cquery.createPersistenceException(e);
// create exception before closing connection
cquery.close();
throw cquery.createPersistenceException(e);
throw pex;
} finally {
request.rollbackTransIfRequired();
}
@@ -259,6 +266,7 @@ public class CQueryEngine {
// order by lower sys period desc
query.order().desc(sysPeriodLower);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
if (request.isLogSql()) {
@@ -327,6 +335,7 @@ public class CQueryEngine {
*/
public <T> SpiResultSet findResultSet(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
boolean fwdOnly;
if (request.isFindIterate()) {
@@ -411,6 +420,7 @@ public class CQueryEngine {
EntityBean bean = null;
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.CountedValue;
import io.ebean.core.type.ScalarDataReader;
import io.ebean.util.JdbcClose;
@@ -18,11 +19,12 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Base compiled query request for single attribute queries.
*/
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, CancelableQuery {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
@@ -65,6 +67,8 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
private final boolean containsCounts;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -111,7 +115,6 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
value = new CountedValue<>(value, dataReader.getLong());
}
result.add(value);
dataReader.resetColumnPosition();
rowCount++;
}
@@ -148,21 +151,27 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
}
private void prepareExecute() throws SQLException {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
lock.lock();
try {
query.checkCancelled();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery());
query.checkCancelled();
}
/**
@@ -195,4 +204,14 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -14,7 +14,7 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
private final CQuery<T> cquery;
private final OrmQueryRequest<T> request;
private boolean closed;
CQueryIteratorSimple(CQuery<T> cquery, OrmQueryRequest<T> request) {
@@ -54,8 +54,4 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
}
}
@Override
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -76,8 +76,4 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
}
}
@Override
public void remove() {
throw new PersistenceException("Remove not allowed");
}
}
@@ -13,7 +13,7 @@ import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.DataBindCapture;
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -13,11 +14,12 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the select row count query.
*/
class CQueryRowCount implements SpiProfileTransactionEvent {
class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -57,6 +59,8 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
private int rowCount;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -110,14 +114,22 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rset = pstmt.executeQuery();
query.checkCancelled();
if (!rset.next()) {
throw new PersistenceException("Expecting 1 row but got none?");
}
@@ -161,4 +173,14 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -10,11 +11,12 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the delete query.
* Executes the update query.
*/
class CQueryUpdate implements SpiProfileTransactionEvent {
class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -45,6 +47,8 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
*/
@@ -82,15 +86,22 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rowCount = pstmt.executeUpdate();
query.checkCancelled();
long executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
request.slowQueryCheck(executionTimeMicros, rowCount);
if (queryPlan.executionTime(executionTimeMicros)) {
@@ -122,4 +133,14 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getName(), rowCount, query.getProfileId());
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -11,11 +11,10 @@ import java.util.HashSet;
class DefaultDbSqlContext implements DbSqlContext {
private static final String COMMA = ", ";
private static final String PERIOD = ".";
private static final int STRING_BUILDER_INITIAL_CAPACITY = 140;
private static final String tableAliasPlaceHolder = "${ta}";
private static final String tableAliasManyPlaceHolder = "${mta}";
private final String columnAliasPrefix;
@@ -111,27 +110,22 @@ class DefaultDbSqlContext implements DbSqlContext {
}
@Override
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) {
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) {
if (tableJoins == null) {
tableJoins = new HashSet<>();
}
String joinKey = table + "-" + a1 + "-" + a2;
if (tableJoins.contains(joinKey)) {
return;
}
tableJoins.add(joinKey);
sb.append(" ").append(type);
boolean addAsOfOnClause = false;
if (draftSupport != null) {
appendTable(table, draftSupport.getDraftTable(table));
} else if (!historyQuery) {
sb.append(" ").append(table).append(" ");
} else {
// check if there is an associated history table and if so
// use the unionAll view - we expect an additional predicate to match
@@ -164,13 +158,17 @@ class DefaultDbSqlContext implements DbSqlContext {
if (addAsOfOnClause) {
sb.append(" and ").append(historySupport.getAsOfPredicate(a2));
}
if (extraWhere != null && !extraWhere.isEmpty()) {
sb.append(" and ");
// we will also need a many-table alias here
sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace(tableAliasManyPlaceHolder, a1));
}
}
private void appendTable(String table, String draftTable) {
if (draftTable != null) {
// there is an associated history table and view so use that
sb.append(" ").append(draftTable).append(" ");
} else {
sb.append(" ").append(table).append(" ");
}
@@ -193,7 +191,6 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public String getRelativePrefix(String propName) {
return currentPrefix == null ? propName : currentPrefix + "." + propName;
}
@@ -228,7 +225,6 @@ class DefaultDbSqlContext implements DbSqlContext {
// the same join has already been added.
return;
}
// we only want to add this join once
formulaJoins.add(converted);
sb.append(" ");
@@ -263,13 +259,10 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public void appendHistorySysPeriod() {
String tableAlias = tableAliasStack.peek();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodLower(tableAlias));
appendColumnAlias();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodUpper(tableAlias));
appendColumnAlias();
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
@@ -10,10 +11,10 @@ import io.ebean.metric.TimedMetricMap;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.RelationalQueryEngine;
import io.ebeaninternal.server.core.RelationalQueryRequest;
import io.ebeaninternal.server.core.RowReader;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -59,12 +60,26 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer) {
public void findEach(RelationalQueryRequest request, RowConsumer consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
request.mapEach(consumer);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> void findEach(RelationalQueryRequest request, RowReader<T> reader, Predicate<T> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
if (!consumer.test(readRow(request))) {
if (!consumer.test(reader.read())) {
break;
}
}
@@ -79,25 +94,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public void findEach(RelationalQueryRequest request, Consumer<SqlRow> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
consumer.accept(readRow(request));
}
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
public <T> T findOne(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.BEAN);
T value = request.mapOne(mapper);
@@ -113,13 +110,15 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
@Override
public <T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
public <T> List<T> findList(RelationalQueryRequest request, RowReader<T> reader) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<T> list = request.mapList(mapper);
List<T> rows = new ArrayList<>();
while (request.next()) {
rows.add(reader.read());
}
request.logSummary();
return list;
return rows;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
@@ -128,12 +127,19 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
@SuppressWarnings("unchecked")
@Override
public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) {
public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
try {
request.executeSql(binder, SpiQuery.Type.LIST);
request.mapEach(consumer);
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.getResultSet());
T value = null;
if (dataReader.next()) {
value = scalarType.read(dataReader);
}
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
@@ -147,68 +153,13 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
@Override
public <T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalarList(request, scalarType);
}
private <T> List<T> findScalarList(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
List<T> list = new ArrayList<>();
while (request.next()) {
request.incrementRows();
list.add(scalarType.read(binder.createDataReader(request.getResultSet())));
final DataReader dataReader = binder.createDataReader(request.getResultSet());
List<T> rows = new ArrayList<>();
while (dataReader.next()) {
rows.add(scalarType.read(dataReader));
}
request.logSummary();
return list;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalar(request, scalarType);
}
private <T> T findScalar(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
T value = null;
if (request.next()) {
request.incrementRows();
value = scalarType.read(binder.createDataReader(request.getResultSet()));
}
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public List<SqlRow> findList(RelationalQueryRequest request) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<SqlRow> rows = new ArrayList<>();
while (request.next()) {
rows.add(readRow(request));
}
request.logSummary();
return rows;
@@ -220,11 +171,23 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
/**
* Read the row from the ResultSet and return as a MapBean.
*/
private SqlRow readRow(RelationalQueryRequest request) throws SQLException {
return request.createNewRow();
}
@SuppressWarnings("unchecked")
@Override
public <T> void findSingleAttributeEach(RelationalQueryRequest request, Class<T> cls, Consumer<T> consumer) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.getResultSet());
while (dataReader.next()) {
consumer.accept(scalarType.read(dataReader));
}
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
}
@@ -1,10 +1,12 @@
package io.ebeaninternal.server.query;
import io.ebean.QueryIterator;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DtoQueryRequest;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -27,20 +29,29 @@ public class DtoQueryEngine {
}
return rows;
} catch (Throwable e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
public <T> QueryIterator<T> findIterate(DtoQueryRequest<T> request) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
return new DtoQueryIterator<>(request);
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
}
}
public <T> void findEach(DtoQueryRequest<T> request, Consumer<T> consumer) {
try {
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
consumer.accept(request.readNextBean());
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
@@ -77,9 +88,8 @@ public class DtoQueryEngine {
break;
}
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.query;
import io.ebean.QueryIterator;
import io.ebeaninternal.server.core.DtoQueryRequest;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
class DtoQueryIterator<T> implements QueryIterator<T> {
private final DtoQueryRequest<T> request;
private boolean closed;
DtoQueryIterator(DtoQueryRequest<T> request) {
this.request = request;
}
@Override
public boolean hasNext() {
boolean result = false;
try {
result = request.next();
return result;
} catch (SQLException e) {
throw new PersistenceException(e);
} finally {
if (!result) {
close();
}
}
}
@Override
public T next() {
try {
return request.readNextBean();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
@Override
public void close() {
if (!closed) {
closed = true;
request.close();
request.endTransIfRequired();
}
}
}
@@ -47,16 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
ctx.popTableAlias();
}
/**
* append extraWhere to the join.
*/
@Override
protected SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) {
SqlJoinType join = super.appendFromAsJoin(ctx, joinType);
super.appendExtraWhere(ctx);
return join;
}
@Override
protected void appendExtraWhere(DbSqlContext ctx) {
// extraWhere is already appended to the tableJoin
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.querydefn;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
import io.ebeaninternal.api.SpiCancelableQuery;
/**
* Common code for Dto/Orm/RelationalQuery
*
* @author Roland Praml, FOCONIS AG
*
*/
public class AbstractQuery implements SpiCancelableQuery {
private boolean cancelled;
private CancelableQuery cancelableQuery;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled) {
cancelled = true;
if (cancelableQuery != null) {
cancelableQuery.cancel();
}
}
} finally {
lock.unlock();
}
}
@Override
public void checkCancelled() {
if (cancelled) {
throw new PersistenceException("Query was cancelled");
}
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
checkCancelled();
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.DtoQuery;
import io.ebean.ProfileLocation;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiDtoQuery;
@@ -15,11 +16,12 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Default implementation of DtoQuery.
*/
public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
public class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQuery<T> {
private final SpiEbeanServer server;
@@ -112,6 +114,16 @@ public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
server.findDtoEachWhile(this, consumer);
}
@Override
public QueryIterator<T> findIterate() {
return server.findDtoIterate(this);
}
@Override
public Stream<T> findStream() {
return server.findDtoStream(this);
}
@Override
public List<T> findList() {
return server.findDtoList(this);
@@ -55,10 +55,10 @@ import io.ebeaninternal.server.deploy.BeanNaturalKey;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
@@ -81,7 +81,7 @@ import java.util.stream.Stream;
/**
* Default implementation of an Object Relational query.
*/
public class DefaultOrmQuery<T> implements SpiQuery<T> {
public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private static final String DEFAULT_QUERY_NAME = "default";
@@ -113,10 +113,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private ProfilingListener profilingListener;
private boolean cancelled;
private CancelableQuery cancelableQuery;
private Type type;
private String label;
@@ -539,6 +535,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (havingExpressions != null) {
havingExpressions.containsMany(beanDescriptor, manyWhereJoins);
}
if (orderBy != null) {
for (Property orderProperty : orderBy.getProperties()) {
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(orderProperty.getProperty());
if (elProp != null && elProp.containsFormulaWithJoin()) {
manyWhereJoins.addFormulaWithJoin(orderProperty.getProperty());
}
}
}
}
/**
@@ -856,6 +860,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
copy.parentNode = parentNode;
copy.forUpdate = forUpdate;
copy.rawSql = rawSql;
setCancelableQuery(copy); // required to cancel findId query
return copy;
}
@@ -2046,16 +2051,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return futureFetchAudit;
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
@Override
public Query<T> setBaseTable(String baseTable) {
this.baseTable = baseTable;
@@ -2083,28 +2078,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return rootTableAlias != null ? rootTableAlias : defaultAlias;
}
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled && cancelableQuery != null) {
cancelled = true;
cancelableQuery.cancel();
}
} finally {
lock.unlock();
}
}
@Override
public boolean isCancelled() {
lock.lock();
try {
return cancelled;
} finally {
lock.unlock();
}
}
@Override
public Set<String> validate() {
@@ -17,15 +17,15 @@ import java.util.function.Predicate;
/**
* Default implementation of SQuery - SQL Query.
*/
public class DefaultRelationalQuery implements SpiSqlQuery {
public class DefaultRelationalQuery extends AbstractQuery implements SpiSqlQuery {
private static final long serialVersionUID = -1098305779779591068L;
private final transient SpiEbeanServer server;
private String label;
private final String query;
private String query;
private String label;
private int firstRow;
@@ -208,19 +208,25 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
return query;
}
<T> T mapperFindOne(RowMapper<T> mapper) {
private <T> T mapperFindOne(RowMapper<T> mapper) {
return server.findOneMapper(this, mapper);
}
<T> List<T> mapperFindList(RowMapper<T> mapper) {
private <T> List<T> mapperFindList(RowMapper<T> mapper) {
return server.findListMapper(this, mapper);
}
private <T> void mapperFindEach(RowMapper<T> mapper, Consumer<T> consumer) {
server.findEachRow(this, (resultSet, rowNum) -> consumer.accept(mapper.map(resultSet, rowNum)));
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <T> TypeQuery<T> mapToScalar(Class<T> attributeType) {
return new Scalar(attributeType);
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <T> TypeQuery<T> mapTo(RowMapper<T> mapper) {
return new Mapper(mapper);
@@ -248,6 +254,15 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
public List<T> findList() {
return findSingleAttributeList(type);
}
@Override
public void findEach(Consumer<T> consumer) {
scalarFindEach(type, consumer);
}
}
private <T> void scalarFindEach(Class<T> type, Consumer<T> consumer) {
server.findSingleAttributeEach(this, type, consumer);
}
private class Mapper<T> implements SqlQuery.TypeQuery<T> {
@@ -272,5 +287,10 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
public List<T> findList() {
return mapperFindList(mapper);
}
@Override
public void findEach(Consumer<T> consumer) {
mapperFindEach(mapper, consumer);
}
}
}
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.query.DefaultSqlRow;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
public class DRawSqlService implements SpiRawSqlService {
@@ -48,7 +49,26 @@ public class DRawSqlService implements SpiRawSqlService {
if (ret.containsKey(name)) {
name = combine(meta.getSchemaName(i), meta.getTableName(i), name);
}
ret.put(name, resultSet.getObject(i));
// convert (C/B)LOBs to java objects.
// A java.sql.Clob depends on an open connection, so storing this object in a map
// that is accessed later, when the connection is closed, will result in a "connection is closed" exception.
// From the java.sql.Clob documentation: "... which means that a Clob object contains a logical pointer to the SQL CLOB
// data rather than the data itself."
switch (meta.getColumnType(i)) {
case Types.CLOB:
case Types.NCLOB:
ret.put(name, resultSet.getString(i));
break;
case Types.BLOB:
ret.put(name, resultSet.getBytes(i));
break;
default:
ret.put(name, resultSet.getObject(i));
break;
}
}
return ret;
}
@@ -2,6 +2,9 @@ package io.ebeaninternal.server.transaction;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiBeanType;
import io.ebeaninternal.api.SpiBeanTypeManager;
import io.ebeaninternal.api.SpiPersistenceContext;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@@ -11,19 +14,16 @@ import java.util.concurrent.locks.ReentrantLock;
* <p>
* Ensures only one instance of a bean is used according to its type and unique
* id.
* </p>
* <p>
* PersistenceContext lives on a Transaction and as such is expected to only
* have a single thread accessing it at a time. This is not expected to be used
* concurrently.
* </p>
* <p>
* Duplicate beans are ones having the same type and unique id value. These are
* considered duplicates and replaced by the bean instance that was already
* loaded into the PersistenceContext.
* </p>
*/
public final class DefaultPersistenceContext implements PersistenceContext {
public final class DefaultPersistenceContext implements SpiPersistenceContext {
/**
* Map used hold caches. One cache per bean type.
@@ -199,12 +199,12 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
@Override
public List<Object> dirtyBeans() {
public List<Object> dirtyBeans(SpiBeanTypeManager manager) {
lock.lock();
try {
List<Object> list = new ArrayList<>();
for (ClassContext classContext : typeCache.values()) {
classContext.dirtyBeans(list);
classContext.dirtyBeans(manager, list);
}
return list;
} finally {
@@ -223,26 +223,26 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
private ClassContext getClassContext(Class<?> rootType) {
return typeCache.computeIfAbsent(rootType, k -> new ClassContext());
return typeCache.computeIfAbsent(rootType, k -> new ClassContext(rootType));
}
private static class ClassContext {
private final Map<Object, Object> map = new HashMap<>();
private final Class<?> rootType;
private Set<Object> deleteSet;
private int initialSize;
private ClassContext parent;
private ClassContext() {
private ClassContext(Class<?> rootType) {
this.rootType = rootType;
}
/**
* Create as a shallow copy.
*/
private ClassContext(ClassContext source, boolean initial) {
this.rootType = source.rootType;
if (initial || source.isTransfer()) {
parent = source.transferParent();
initialSize = parent.size();
@@ -334,9 +334,11 @@ public final class DefaultPersistenceContext implements PersistenceContext {
/**
* Add the dirty beans to the list.
*/
void dirtyBeans(List<Object> list) {
void dirtyBeans(SpiBeanTypeManager manager, List<Object> list) {
final SpiBeanType beanType = manager.getBeanType(rootType);
for (Object value : map.values()) {
if (((EntityBean) value)._ebean_getIntercept().isDirty()) {
EntityBean bean = (EntityBean) value;
if (bean._ebean_getIntercept().isDirty() || beanType.isToManyDirty(bean)) {
list.add(value);
}
}
@@ -3,13 +3,9 @@ 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.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
@@ -58,7 +54,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
/**
* Holder of the objects fetched to ensure unique objects are used.
*/
private PersistenceContext persistenceContext;
private SpiPersistenceContext persistenceContext;
private Object tenantId;
@@ -421,7 +417,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -434,7 +430,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
@@ -3,20 +3,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.config.DatabaseConfig;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.util.Str;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -25,12 +21,7 @@ import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Consumer;
/**
@@ -99,7 +90,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
/**
* Holder of the objects fetched to ensure unique objects are used.
*/
private PersistenceContext persistenceContext;
private SpiPersistenceContext persistenceContext;
/**
* Used to give developers more control over the insert update and delete
@@ -806,7 +797,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -816,10 +807,9 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* This could be considered similar to EJB3 Extended PersistanceContext. In
* that you get the PersistanceContext from a transaction, hold onto it, and
* then set it back later to a second transaction.
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
@@ -951,11 +941,13 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
private void connectionEndForQueryOnly() {
try {
withEachCallback(TransactionCallback::preCommit);
if (onQueryOnly == OnQueryOnly.COMMIT) {
performCommit();
} else {
performRollback();
}
withEachCallback(TransactionCallback::postCommit);
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
}
@@ -3,9 +3,9 @@ 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.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.api.SpiPersistenceContext;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
@@ -349,12 +349,12 @@ class NoTransaction implements SpiTransaction {
}
@Override
public PersistenceContext getPersistenceContext() {
public SpiPersistenceContext getPersistenceContext() {
return null;
}
@Override
public void setPersistenceContext(PersistenceContext context) {
public void setPersistenceContext(SpiPersistenceContext context) {
}
@Override
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionProxy;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.util.Str;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -5,7 +5,6 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
@@ -19,15 +18,7 @@ import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
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.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
@@ -770,8 +761,8 @@ public class TransactionManager implements SpiTransactionManager {
/**
* Experimental - find dirty beans in the persistence context and persist them.
*/
public void flushTransparent(PersistenceContext persistenceContext, SpiTransaction transaction) {
List<Object> dirtyBeans = persistenceContext.dirtyBeans();
public void flushTransparent(SpiPersistenceContext persistenceContext, SpiTransaction transaction) {
List<Object> dirtyBeans = persistenceContext.dirtyBeans(beanDescriptorManager);
if (!dirtyBeans.isEmpty()) {
server.updateAll(dirtyBeans, transaction);
}
@@ -1,26 +0,0 @@
package io.ebeaninternal.server.type;
import io.ebean.ModifyAwareType;
/**
* Check dirty state of json value which might be modify aware.
*/
class CheckMarkedDirty {
/**
* Return true if the value should be considered dirty (and included in an update).
*/
static boolean isDirty(Object value) {
if (value instanceof ModifyAwareType) {
ModifyAwareType modifyAware = (ModifyAwareType) value;
if (modifyAware.isMarkedDirty()) {
// reset the dirty state (consider not dirty after update)
modifyAware.setMarkedDirty(false);
return true;
} else {
return false;
}
}
return true;
}
}
@@ -3,11 +3,7 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
import io.ebean.annotation.EnumValue;
import io.ebean.annotation.Platform;
import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.JsonConfig;
import io.ebean.config.PlatformConfig;
@@ -34,125 +30,70 @@ import org.slf4j.LoggerFactory;
import javax.persistence.AttributeConverter;
import javax.persistence.EnumType;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.*;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Currency;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.time.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Default implementation of TypeManager.
* <p>
* Manages the list of ScalarType that is available.
* </p>
*/
public final class DefaultTypeManager implements TypeManager {
private static final Logger logger = LoggerFactory.getLogger(DefaultTypeManager.class);
private final ConcurrentHashMap<Class<?>, ScalarType<?>> typeMap;
private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap;
private final ConcurrentHashMap<String, ScalarType<?>> logicalMap;
private final DefaultTypeFactory extraTypeFactory;
private final ScalarType<?> hstoreType = new ScalarTypePostgresHstore();
private final ScalarTypeFile fileType = new ScalarTypeFile();
private final ScalarType<?> charType = new ScalarTypeChar();
private final ScalarType<?> charArrayType = new ScalarTypeCharArray();
private final ScalarType<?> longVarcharType = new ScalarTypeLongVarchar();
private final ScalarType<?> clobType = new ScalarTypeClob();
private final ScalarType<?> byteType = new ScalarTypeByte();
private final ScalarType<?> binaryType = new ScalarTypeBytesBinary();
private final ScalarType<?> blobType = new ScalarTypeBytesBlob();
private final ScalarType<?> varbinaryType = new ScalarTypeBytesVarbinary();
private final ScalarType<?> longVarbinaryType = new ScalarTypeBytesLongVarbinary();
private final ScalarType<?> shortType = new ScalarTypeShort();
private final ScalarType<?> integerType = ScalarTypeInteger.INSTANCE;
private final ScalarType<?> longType = new ScalarTypeLong();
private final ScalarType<?> doubleType = new ScalarTypeDouble();
private final ScalarType<?> floatType = new ScalarTypeFloat();
private final ScalarType<?> bigDecimalType = new ScalarTypeBigDecimal();
private final ScalarType<?> timeType = new ScalarTypeTime();
private final ScalarType<?> urlType = new ScalarTypeURL();
private final ScalarType<?> uriType = new ScalarTypeURI();
private final ScalarType<?> localeType = new ScalarTypeLocale();
private final ScalarType<?> currencyType = new ScalarTypeCurrency();
private final ScalarType<?> timeZoneType = new ScalarTypeTimeZone();
private final ScalarType<?> stringType = ScalarTypeString.INSTANCE;
private final ScalarType<?> classType = new ScalarTypeClass();
private final JsonConfig.DateTime jsonDateTime;
private final JsonConfig.Date jsonDate;
private final Object objectMapper;
private final boolean objectMapperPresent;
private final boolean postgres;
private final TypeJsonManager jsonManager;
private final boolean offlineMigrationGeneration;
private final EnumType defaultEnumType;
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
@@ -191,24 +132,20 @@ public final class DefaultTypeManager implements TypeManager {
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
this.postgres = isPostgres(config.getDatabasePlatform());
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null;
this.extraTypeFactory = new DefaultTypeFactory(config);
this.postgres = isPostgres(config.getDatabasePlatform());
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
this.defaultEnumType = config.getDefaultEnumType();
initialiseStandard(config);
initialiseJavaTimeTypes(config);
initialiseJodaTypes(config);
initialiseJacksonTypes(config);
loadTypesFromProviders(config, objectMapper);
loadGeoTypeBinder(config);
@@ -489,7 +426,7 @@ public final class DefaultTypeManager implements TypeManager {
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType);
return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType);
}
/**
@@ -588,8 +525,7 @@ public final class DefaultTypeManager implements TypeManager {
private ScalarTypeEnum<?> createEnumScalarType2(Class<?> enumType) {
boolean integerType = true;
Map<String, String> nameValueMap = new LinkedHashMap<>();
Field[] fields = enumType.getDeclaredFields();
for (Field field : fields) {
for (Field field : enumType.getDeclaredFields()) {
EnumValue enumValue = AnnotationUtil.get(field, EnumValue.class);
if (enumValue != null) {
nameValueMap.put(field.getName(), enumValue.value());
@@ -652,8 +588,7 @@ public final class DefaultTypeManager implements TypeManager {
}
private ScalarTypeEnum<?> createEnumScalarTypePerExtentions(Class<? extends Enum<?>> enumType) {
Method[] methods = enumType.getMethods();
for (Method method : methods) {
for (Method method : enumType.getMethods()) {
DbEnumValue dbValue = AnnotationUtil.get(method, DbEnumValue.class);
if (dbValue != null) {
boolean integerValues = DbEnumType.INTEGER == dbValue.storage();
@@ -668,7 +603,6 @@ public final class DefaultTypeManager implements TypeManager {
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
* <p>
* Return null if the EnumValue annotations are not present/used.
* </p>
*/
private ScalarTypeEnum<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType, int length, boolean withConstraint) {
Map<String, String> nameValueMap = new LinkedHashMap<>();
@@ -697,11 +631,11 @@ public final class DefaultTypeManager implements TypeManager {
EnumToDbValueMap<?> beanDbMap = EnumToDbValueMap.create(integerType);
int maxValueLen = 0;
for (Map.Entry<String, String> entry : nameValueMap.entrySet()) {
String name = entry.getKey();
String name = entry.getKey().trim();
String value = entry.getValue();
maxValueLen = Math.max(maxValueLen, value.length());
Object enumValue = Enum.valueOf(enumType, name.trim());
beanDbMap.add(enumValue, value, name.trim());
Object enumValue = Enum.valueOf(enumType, name);
beanDbMap.add(enumValue, value, name);
}
if (dbColumnLength == 0 && !integerType) {
dbColumnLength = maxValueLen;
@@ -711,31 +645,24 @@ public final class DefaultTypeManager implements TypeManager {
/**
* Automatically find any ScalarTypes by searching through the class path.
* <p>
* In avaje.properties define a list of packages in which ScalarTypes are
* found. This will search for any class that implements the ScalarType
* interface and register it with this TypeManager.
* </p>
*/
private void initialiseCustomScalarTypes(BootupClasses bootupClasses) {
for (Class<? extends ScalarType<?>> cls : bootupClasses.getScalarTypes()) {
try {
ScalarType<?> scalarType;
if (objectMapper == null) {
scalarType = cls.newInstance();
scalarType = cls.getDeclaredConstructor().newInstance();
} else {
try {
// first try objectMapper constructor
Constructor<? extends ScalarType<?>> constructor = cls.getConstructor(ObjectMapper.class);
scalarType = constructor.newInstance((ObjectMapper) objectMapper);
scalarType = cls.getDeclaredConstructor(ObjectMapper.class).newInstance(objectMapper);
} catch (NoSuchMethodException e) {
scalarType = cls.newInstance();
scalarType = cls.getDeclaredConstructor().newInstance();
}
}
addCustomType(scalarType);
} catch (Exception e) {
String msg = "Error loading ScalarType [" + cls.getName() + "]";
logger.error(msg, e);
logger.error("Error loading ScalarType [" + cls.getName() + "]", e);
}
}
}
@@ -755,8 +682,7 @@ public final class DefaultTypeManager implements TypeManager {
@SuppressWarnings({"unchecked", "rawtypes"})
private void initialiseScalarConverters(BootupClasses bootupClasses) {
List<Class<? extends ScalarTypeConverter<?, ?>>> foundTypes = bootupClasses.getScalarConverters();
for (Class<? extends ScalarTypeConverter<?, ?>> foundType : foundTypes) {
for (Class<? extends ScalarTypeConverter<?, ?>> foundType : bootupClasses.getScalarConverters()) {
try {
Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, ScalarTypeConverter.class);
if (paramTypes.length != 2) {
@@ -768,7 +694,7 @@ public final class DefaultTypeManager implements TypeManager {
if (wrappedType == null) {
throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]);
}
ScalarTypeConverter converter = foundType.newInstance();
ScalarTypeConverter converter = foundType.getDeclaredConstructor().newInstance();
ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, converter);
logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType);
add(stw);
@@ -780,8 +706,7 @@ public final class DefaultTypeManager implements TypeManager {
@SuppressWarnings({"unchecked", "rawtypes"})
private void initialiseAttributeConverters(BootupClasses bootupClasses) {
List<Class<? extends AttributeConverter<?, ?>>> foundTypes = bootupClasses.getAttributeConverters();
for (Class<? extends AttributeConverter<?, ?>> foundType : foundTypes) {
for (Class<? extends AttributeConverter<?, ?>> foundType : bootupClasses.getAttributeConverters()) {
try {
Class<?>[] paramTypes = TypeReflectHelper.getParams(foundType, AttributeConverter.class);
if (paramTypes.length != 2) {
@@ -793,7 +718,7 @@ public final class DefaultTypeManager implements TypeManager {
if (wrappedType == null) {
throw new IllegalStateException("Could not find ScalarType for: " + paramTypes[1]);
}
AttributeConverter converter = foundType.newInstance();
AttributeConverter converter = foundType.getDeclaredConstructor().newInstance();
ScalarTypeWrapper stw = new ScalarTypeWrapper(logicalType, wrappedType, new AttributeConverterAdapter(converter));
logger.debug("Register ScalarTypeWrapper from {} -> {} using:{}", logicalType, persistType, foundType);
add(stw);
@@ -825,12 +750,15 @@ public final class DefaultTypeManager implements TypeManager {
}
private void initialiseJavaTimeTypes(DatabaseConfig config) {
ZoneId zoneId = getZoneId(config);
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
addType(java.time.Period.class, new ScalarTypePeriod());
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId));
addType(Instant.class, new ScalarTypeInstant(jsonDateTime));
addType(DayOfWeek.class, new ScalarTypeDayOfWeek());
addType(Month.class, new ScalarTypeMonth());
@@ -846,6 +774,11 @@ public final class DefaultTypeManager implements TypeManager {
addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration());
}
private ZoneId getZoneId(DatabaseConfig config) {
final String dataTimeZone = config.getDataTimeZone();
return (dataTimeZone == null) ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(dataTimeZone).toZoneId();
}
private void addType(Class<?> clazz, ScalarType<?> scalarType) {
typeMap.put(clazz, scalarType);
logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType);
@@ -890,23 +823,16 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(DbPlatformType.HSTORE, hstoreType);
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate(jsonDateTime, jsonDate);
addType(java.util.Date.class, utilDateType);
ScalarType<?> calType = extraTypeFactory.createCalendar(jsonDateTime);
addType(Calendar.class, calType);
ScalarType<?> mathBigIntType = extraTypeFactory.createMathBigInteger();
addType(BigInteger.class, mathBigIntType);
addType(java.util.Date.class, extraTypeFactory.createUtilDate(jsonDateTime, jsonDate));
addType(Calendar.class, extraTypeFactory.createCalendar(jsonDateTime));
addType(BigInteger.class, extraTypeFactory.createMathBigInteger());
ScalarTypeBool booleanType = extraTypeFactory.createBoolean();
addType(Boolean.class, booleanType);
addType(boolean.class, booleanType);
// register the boolean literals to the platform for DDL default values
databasePlatform.setDbTrueLiteral(booleanType.getDbTrueLiteral());
databasePlatform.setDbFalseLiteral(booleanType.getDbFalseLiteral());
// always register Types.BOOLEAN to our boolean type
nativeMap.put(Types.BOOLEAN, booleanType);
if (booleanType.getJdbcType() == Types.BIT) {
@@ -915,7 +841,6 @@ public final class DefaultTypeManager implements TypeManager {
}
PlatformConfig.DbUuid dbUuid = config.getPlatformConfig().getDbUuid();
if (offlineMigrationGeneration || (databasePlatform.isNativeUuidType() && dbUuid.useNativeType())) {
addType(UUID.class, new ScalarTypeUUIDNative());
} else {
@@ -1027,6 +952,7 @@ public final class DefaultTypeManager implements TypeManager {
nativeMap.put(Types.TIMESTAMP, timestampType);
}
@SuppressWarnings("rawtypes")
private void addInetAddressType(ScalarType scalarType) {
addType(InetAddress.class, scalarType);
addType(Inet4Address.class, scalarType);
@@ -0,0 +1,46 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
class JacksonTypeHelper {
private final AnnotatedField field;
private final ObjectMapper objectMapper;
private final JavaType javaType;
private final DeserializationConfig deserConfig;
private final AnnotationIntrospector ai;
JacksonTypeHelper(AnnotatedField field, ObjectMapper objectMapper) {
this.field = field;
this.objectMapper = objectMapper;
this.javaType = field.getType();
this.deserConfig = objectMapper.getDeserializationConfig();
this.ai = deserConfig.getAnnotationIntrospector();
}
JavaType type() {
if (ai == null || javaType == null || javaType.hasRawClass(Object.class)) {
return javaType;
} else {
try {
return ai.refineDeserializationType(deserConfig, field, javaType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
}
}
ObjectWriter objectWriter() {
if (ai == null || javaType == null || javaType.hasRawClass(Object.class)) {
return objectMapper.writerFor(javaType);
} else {
try {
JavaType serType = ai.refineSerializationType(objectMapper.getSerializationConfig(), field, javaType);
return objectMapper.writerFor(serType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -43,12 +43,8 @@ public class RsetDataReader implements DataReader {
@Override
public boolean next() throws SQLException {
return rset.next();
}
@Override
public void resetColumnPosition() {
pos = 0;
return rset.next();
}
@Override
@@ -75,7 +71,6 @@ public class RsetDataReader implements DataReader {
return rset.getBigDecimal(pos());
}
@Override
public InputStream getBinaryStream() throws SQLException {
return rset.getBinaryStream(pos());
@@ -84,19 +79,13 @@ public class RsetDataReader implements DataReader {
@Override
public Boolean getBoolean() throws SQLException {
boolean v = rset.getBoolean(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
@Override
public Byte getByte() throws SQLException {
byte v = rset.getByte(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
@Override
@@ -117,62 +106,42 @@ public class RsetDataReader implements DataReader {
@Override
public Double getDouble() throws SQLException {
double v = rset.getDouble(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
@Override
public Float getFloat() throws SQLException {
float v = rset.getFloat(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
@Override
public Integer getInt() throws SQLException {
int v = rset.getInt(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
@Override
public Long getLong() throws SQLException {
long v = rset.getLong(pos());
if (rset.wasNull()) {
return null;
}
return v;
return rset.wasNull() ? null : v;
}
public Ref getRef() throws SQLException {
return rset.getRef(pos());
}
@Override
public Short getShort() throws SQLException {
short s = rset.getShort(pos());
if (rset.wasNull()) {
return null;
}
return s;
return rset.wasNull() ? null : s;
}
@Override
public String getString() throws SQLException {
return rset.getString(pos());
}
@Override
public Time getTime() throws SQLException {
Calendar cal = dataTimeZone.getTimeZone();
@@ -196,14 +165,10 @@ public class RsetDataReader implements DataReader {
@Override
public String getStringFromStream() throws SQLException {
Reader reader = rset.getCharacterStream(pos());
if (reader == null) {
return null;
}
return readStringLob(reader);
return reader == null ? null : readStringLob(reader);
}
protected String readStringLob(Reader reader) throws SQLException {
char[] buffer = new char[clobBufferSize];
int readLength;
StringBuilder out = new StringBuilder(stringInitialSize);
@@ -215,7 +180,6 @@ public class RsetDataReader implements DataReader {
} catch (IOException e) {
throw new SQLException("IOException reading Clob " + e.getMessage());
}
return out.toString();
}
@@ -226,19 +190,16 @@ public class RsetDataReader implements DataReader {
}
protected byte[] getBinaryLob(InputStream in) throws SQLException {
if (in == null) {
return null;
}
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buf = new byte[bufferSize];
int len;
while ((len = in.read(buf, 0, buf.length)) != -1) {
out.write(buf, 0, len);
}
byte[] data = out.toByteArray();
if (data.length == 0) {
data = null;
}
@@ -61,7 +61,7 @@ abstract class ScalarTypeJsonCollection<T> extends ScalarTypeBase<T> implements
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}
@Override
@@ -123,7 +123,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase<Map> {
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}
@Override
@@ -3,15 +3,10 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -34,38 +29,23 @@ import java.util.Set;
/**
* Supports @DbJson properties using Jackson ObjectMapper.
*/
public class ScalarTypeJsonObjectMapper {
class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
*/
public static ScalarType<?> createTypeFor(boolean postgres, AnnotatedField field, ObjectMapper objectMapper,
int dbType, DocPropertyType docType) {
static ScalarType<?> createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
Class<?> type = field.getRawType();
String pgType = getPostgresType(postgres, dbType);
if (Set.class.equals(type)) {
return new OmSet(objectMapper, field, dbType, pgType, docType);
return new OmSet(jsonManager, field, dbType, docType);
}
if (List.class.equals(type)) {
return new OmList(objectMapper, field, dbType, pgType, docType);
return new OmList(jsonManager, field, dbType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(objectMapper, field, dbType, pgType);
return new OmMap(jsonManager, field, dbType);
}
return new GenericObject(objectMapper, field, dbType, pgType);
}
private static String getPostgresType(boolean postgres, int dbType) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSON:
return PostgresHelper.JSON_TYPE;
case DbPlatformType.JSONB:
return PostgresHelper.JSONB_TYPE;
}
}
return null;
return new GenericObject(jsonManager, field, dbType, type);
}
/**
@@ -73,8 +53,8 @@ public class ScalarTypeJsonObjectMapper {
*/
private static class GenericObject extends Base<Object> {
public GenericObject(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Object.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class<?> rawType) {
super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType);
}
}
@@ -84,8 +64,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmSet extends Base<Set> {
public OmSet(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(Set.class, objectMapper, field, dbType, pgType, docType);
OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Set.class, jsonManager, field, dbType, docType);
}
@Override
@@ -102,8 +82,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmList extends Base<List> {
public OmList(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(List.class, objectMapper, field, dbType, pgType, docType);
OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(List.class, jsonManager, field, dbType, docType);
}
@Override
@@ -120,8 +100,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmMap extends Base<Map> {
public OmMap(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Map.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) {
super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT);
}
@Override
@@ -139,51 +119,25 @@ public class ScalarTypeJsonObjectMapper {
private static abstract class Base<T> extends ScalarTypeBase<T> {
private final ObjectWriter objectWriter;
private final ObjectMapper objectReader;
private JavaType deserType;
private final JavaType deserType;
private final String pgType;
private final DocPropertyType docType;
private final TypeJsonManager.DirtyHandler dirtyHandler;
/**
* Construct given the object mapper, property type and DB type for storage.
*/
public Base(Class<T> cls, ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
this(cls, jsonManager, field, dbType, docType, cls);
}
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType, Class<?> rawType) {
super(cls, false, dbType);
this.pgType = pgType;
this.objectReader = jsonManager.objectMapper();
this.pgType = jsonManager.postgresType(dbType);
this.docType = docType;
this.objectReader = objectMapper;
JavaType javaType = field.getType();
DeserializationConfig deserConfig = objectMapper.getDeserializationConfig();
AnnotationIntrospector ai = deserConfig.getAnnotationIntrospector();
if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) {
try {
this.deserType = ai.refineDeserializationType(deserConfig, field, javaType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
} else {
this.deserType = javaType;
}
SerializationConfig serConfig = objectMapper.getSerializationConfig();
ai = deserConfig.getAnnotationIntrospector();
if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) {
try {
JavaType serType = ai.refineSerializationType(serConfig, field, javaType);
this.objectWriter = objectMapper.writerFor(serType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
} else {
this.objectWriter = objectMapper.writerFor(javaType);
}
this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType);
final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader);
this.deserType = helper.type();
this.objectWriter = helper.objectWriter();
}
/**
@@ -199,7 +153,7 @@ public class ScalarTypeJsonObjectMapper {
*/
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return dirtyHandler.isDirty(value);
}
@Override
@@ -224,12 +178,7 @@ public class ScalarTypeJsonObjectMapper {
if (value == null) {
binder.setNull(Types.VARCHAR); // use varchar, otherwise SqlServer/db2 will fail with 'Invalid JDBC data type 5.001.'
} else {
try {
String json = objectWriter.writeValueAsString(value);
binder.setString(json);
} catch (JsonProcessingException e) {
throw new SQLException("Unable to create JSON", e);
}
binder.setString(formatValue(value));
}
}
}
@@ -1,7 +1,10 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.config.JsonConfig;
import java.io.IOException;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
@@ -29,12 +32,37 @@ public class ScalarTypeLocalDateTime extends ScalarTypeBaseDateTime<LocalDateTim
@Override
protected String toJsonNanos(LocalDateTime value) {
return String.valueOf(convertToMillis(value));
return value.toString();
}
@Override
protected String toJsonISO8601(LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.systemDefault()).toInstant().toString();
protected String toJsonISO8601(LocalDateTime value) {
return value.toString();
}
@Override
protected LocalDateTime fromJsonISO8601(String value) {
return LocalDateTime.parse(value);
}
@Override
public LocalDateTime jsonRead(JsonParser parser) throws IOException {
return LocalDateTime.parse(parser.getText());
}
@Override
public void jsonWrite(JsonGenerator writer, LocalDateTime value) throws IOException {
writer.writeString(value.toString());
}
@Override
public String formatValue(LocalDateTime value) {
return value.toString();
}
@Override
public LocalDateTime parse(String value) {
return LocalDateTime.parse(value);
}
@Override
@@ -15,8 +15,11 @@ import static io.ebeaninternal.server.type.IsoJsonDateTimeParser.formatIso;
*/
public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime<OffsetDateTime> {
public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode) {
private final ZoneId zoneId;
public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode, ZoneId zoneId) {
super(mode, OffsetDateTime.class, false, Types.TIMESTAMP);
this.zoneId = zoneId;
}
@Override
@@ -46,7 +49,7 @@ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime<OffsetDateT
@Override
public OffsetDateTime convertFromInstant(Instant ts) {
return OffsetDateTime.ofInstant(ts, ZoneId.systemDefault());
return OffsetDateTime.ofInstant(ts, zoneId);
}
@Override
@@ -33,7 +33,7 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase<Map> {
@Override
public boolean isDirty(Object value) {
return CheckMarkedDirty.isDirty(value);
return TypeJsonManager.checkIsDirty(value);
}
@SuppressWarnings("unchecked")
@@ -13,8 +13,11 @@ import java.time.ZonedDateTime;
*/
public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime<ZonedDateTime> {
public ScalarTypeZonedDateTime(JsonConfig.DateTime mode) {
private final ZoneId zoneId;
public ScalarTypeZonedDateTime(JsonConfig.DateTime mode, ZoneId zoneId) {
super(mode, ZonedDateTime.class, false, Types.TIMESTAMP);
this.zoneId = zoneId;
}
@Override
@@ -44,7 +47,7 @@ public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime<ZonedDateTim
@Override
public ZonedDateTime convertFromInstant(Instant ts) {
return ZonedDateTime.ofInstant(ts, ZoneId.systemDefault());
return ZonedDateTime.ofInstant(ts, zoneId);
}
@Override
@@ -0,0 +1,98 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.ModifyAwareType;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DbPlatformType;
class TypeJsonManager {
interface DirtyHandler {
boolean isDirty(Object value);
}
private final boolean postgres;
private final ObjectMapper objectMapper;
private final DirtyHandler defaultHandler;
private final DirtyHandler modifyAwareHandler;
TypeJsonManager(boolean postgres, Object objectMapper, boolean defaultDirty) {
this.postgres = postgres;
this.objectMapper = (ObjectMapper) objectMapper;
this.defaultHandler = new DefaultHandler(defaultDirty);
this.modifyAwareHandler = new ModifyAwareHandler();
}
ObjectMapper objectMapper() {
return objectMapper;
}
String postgresType(int dbType) {
if (postgres) {
switch (dbType) {
case DbPlatformType.JSON:
return PostgresHelper.JSON_TYPE;
case DbPlatformType.JSONB:
return PostgresHelper.JSONB_TYPE;
}
}
return null;
}
/**
* Return the DirtyHandler to use.
*/
DirtyHandler dirtyHandler(Class<?> cls, Class<?> rawType) {
if (!Object.class.equals(cls) || ModifyAwareType.class.isAssignableFrom(rawType)) {
// Set, List and Map are modify aware
return modifyAwareHandler;
}
return defaultHandler;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
static boolean checkIsDirty(Object value) {
if (value instanceof ModifyAwareType) {
return checkModifyAware(value);
}
return true;
}
private static boolean checkModifyAware(Object value) {
ModifyAwareType modifyAware = (ModifyAwareType) value;
if (modifyAware.isMarkedDirty()) {
// reset the dirty state (consider not dirty after update)
modifyAware.setMarkedDirty(false);
return true;
} else {
return false;
}
}
static final class ModifyAwareHandler implements DirtyHandler {
@Override
public boolean isDirty(Object value) {
return checkModifyAware(value);
}
}
/**
* Effectively constant based on {@link DatabaseConfig#isJsonDirtyByDefault()}
*/
static final class DefaultHandler implements DirtyHandler {
private final boolean dirty;
DefaultHandler(boolean dirty) {
this.dirty = dirty;
}
@Override
public boolean isDirty(Object value) {
return dirty;
}
}
}
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.lib;
package io.ebeaninternal.server.util;
/**
* String utility for adding strings together.
@@ -11,7 +11,6 @@ public class Str {
* Append strings together.
*/
public static String add(String s0, String s1, String... args) {
// determine a decent buffer size
int len = 16 + s0.length();
if (s1 != null) {
@@ -6,10 +6,13 @@ import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
@@ -30,6 +33,73 @@ public class DtoQuery2Test extends BaseTestCase {
assertThat(list).isNotEmpty();
}
@Test
public void dto_findIterator_closeWithResources() {
ResetBasicData.reset();
int counter = 0;
try (QueryIterator<DCust> iterator = server()
.findDto(DCust.class, "select id, name from o_customer where id > ?")
.setParameter(0)
.findIterate()) {
if (iterator.hasNext()) {
counter++;
}
}
assertThat(counter).isEqualTo(1);
}
@Test
public void dto_findIterator() {
ResetBasicData.reset();
final int expectedCount = server().find(Customer.class).findCount();
LoggedSqlCollector.start();
int counter = 0;
try (final QueryIterator<DCust> iterator = server().findDto(DCust.class, "select id, name from o_customer where id > :id")
.setParameter("id", 0)
.findIterate()) {
while (iterator.hasNext()) {
final DCust cust = iterator.next();
counter++;
assertThat(cust).isNotNull();
assertThat(cust.getName()).isNotNull();
}
}
assertThat(counter).isEqualTo(expectedCount);
List<String> sql = LoggedSqlCollector.stop();
assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?");
}
@Test
public void dto_findStream() {
ResetBasicData.reset();
final int expectedCount = server().find(Customer.class).findCount();
LoggedSqlCollector.start();
try (final Stream<DCust> stream =
server()
.findDto(DCust.class, "select id, name from o_customer where id > ?")
.setParameter(0)
.findStream()) {
final List<String> names = stream
.map(DCust::getName)
.collect(Collectors.toList());
assertThat(names.size()).isEqualTo(expectedCount);
}
List<String> sql = LoggedSqlCollector.stop();
assertSql(sql.get(0)).contains("select id, name from o_customer where id > ?");
}
@Test
public void dto_findEach_constructorMatch() {

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