Compare commits

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

* Fixed: Compile

* Refactoring and Testing
2018-05-28 16:54:53 +02:00
rob bygrave c57af29fca [maven-release-plugin] prepare for next development iteration 2018-05-29 00:44:36 +12:00
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
95 changed files with 1927 additions and 246 deletions
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.11</version>
<version>11.17.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.11</tag>
<tag>ebean-11.17.1</tag>
</scm>
<profiles>
@@ -167,6 +167,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
return db().deletePermanent(bean);
}
/**
* Merge this entity using the default merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object)
*/
public void merge(T bean) {
db().merge(bean);
}
/**
* Merge this entity using the specified merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#merge(Object, MergeOptions)
*/
public void merge(T bean, MergeOptions options) {
db().merge(bean, options);
}
/**
* Refreshes this entity from the database.
*
+9
View File
@@ -638,6 +638,15 @@ public final class Ebean {
serverMgr.getDefaultServer().updateAll(beans);
}
/**
* Merge the bean using the default merge options.
*
* @param bean The bean to merge
*/
public static void merge(Object bean) {
serverMgr.getDefaultServer().merge(bean);
}
/**
* Merge the bean using the given merge options.
*
+7
View File
@@ -1628,6 +1628,13 @@ public interface EbeanServer {
*/
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Merge the bean using the default merge options (no paths specified, default delete).
*
* @param bean The bean to merge
*/
void merge(Object bean);
/**
* Merge the bean using the given merge options.
*
@@ -8,11 +8,20 @@ import java.util.Set;
*/
public class MergeOptionsBuilder {
private static final MOptions DEFAULT_OPTIONS = new MOptions();
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent = true;
private boolean deletePermanent;
/**
* Return the default options.
*/
public static MergeOptions defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Add a path that will included in the merge.
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
/**
* Build and return the MergeOptions instance.
*/
public MOptions build() {
public MergeOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
@@ -57,6 +66,12 @@ public class MergeOptionsBuilder {
private final boolean deletePermanent;
private final Set<String> paths;
private MOptions(){
this.clientGeneratedIds = false;
this.paths = new LinkedHashSet<>();
this.deletePermanent = false;
}
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
this.paths = paths;
this.clientGeneratedIds = clientGeneratedIds;
+48 -7
View File
@@ -19,15 +19,17 @@ package io.ebean;
* <p>
* <pre>{@code
*
* // example that uses 'named' parameters
* String s = "UPDATE f_topic set post_count = :count where id = :id";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
* // example that uses 'named' parameters
*
* int modifiedCount = Ebean.execute(update);
* String s = "UPDATE f_topic set post_count = :count where id = :id";
*
* String msg = "There were " + modifiedCount + " rows updated";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
*
* int modifiedCount = update.execute();
*
* String msg = "There were " + modifiedCount + " rows updated";
*
* }</pre>
* <p>
@@ -57,6 +59,33 @@ package io.ebean;
*
* txn.commit();
* }
* }</pre>
* <p>
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
* </p>
* <pre>{@code
*
* try (Transaction txn = Ebean.beginTransaction()) {
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
*
* txn.commit();
* }
*
* }</pre>
*
@@ -83,6 +112,18 @@ public interface SqlUpdate {
*/
int execute();
/**
* Execute when addBatch() has been used to batch multiple bind executions.
*
* @return The row counts for each of the batched statements.
*/
int[] executeBatch();
/**
* Add the statement to batch processing to then later execute via executeBatch().
*/
void addBatch();
/**
* Return the generated key value.
*/
@@ -493,6 +493,11 @@ public class ServerConfig {
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1967,7 +1972,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2772,6 +2777,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -3109,6 +3115,23 @@ public class ServerConfig {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -31,6 +31,11 @@ import java.util.function.Predicate;
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the log manager.
*/
SpiLogManager log();
/**
* Return the server extended Json context.
*/
@@ -268,4 +273,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return true if a row for the bean type and id exists.
*/
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
/**
* Add to JDBC batch for later execution.
*/
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the batched statement.
*/
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
/**
* Log manager for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogManager {
/**
* Return the SQL logger.
*/
SpiLogger sql();
/**
* Return the TXN logger.
*/
SpiLogger txn();
/**
* Return the Summary logger.
*/
SpiLogger sum();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.api;
/**
* Logger for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogger {
/**
* Is debug logging enabled.
*/
boolean isDebug();
/**
* Is trace logging enabled.
*/
boolean isTrace();
/**
* Log a debug level message.
*/
void debug(String msg);
/**
* Log a trace level message.
*/
void trace(String msg);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
*/
public interface SpiLoggerFactory {
/**
* Create the logger given the name.
*/
SpiLogger create(String name);
}
@@ -644,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
boolean isBeanCachePut();
/**
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
* Return true if we must hit the DB (Cache reload or select for update).
*/
boolean isBeanCacheReload();
boolean isForceHitDatabase();
/**
* Return the cache mode for using the bean cache (Get and Put).
@@ -5,6 +5,7 @@ import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
@@ -48,10 +49,12 @@ class ModelBuildIntersectionTable {
private void buildFkConstraints() {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
PropertyForeignKey foreignKey = manyProp.getForeignKey();
if (foreignKey == null || !foreignKey.isNoConstraint()) {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
}
intersectionTable.checkDuplicateForeignKeys();
}
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
@@ -170,11 +170,7 @@ public abstract class AbstractSqlQueryRequest {
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
@@ -13,6 +13,7 @@ import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.MergeOptions;
import io.ebean.MergeOptionsBuilder;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
@@ -63,8 +64,10 @@ import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEventTable;
@@ -189,6 +192,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
@@ -229,6 +234,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
@@ -315,6 +321,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public SpiLogManager log() {
return logManager;
}
@Override
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
@@ -919,6 +930,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultUpdateQuery<>(createQuery(beanType));
}
@Override
public void merge(Object bean) {
merge(bean, MergeOptionsBuilder.defaultOptions(), null);
}
@Override
public void merge(Object bean, MergeOptions options) {
merge(bean, options, null);
@@ -1159,7 +1175,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
@@ -1961,6 +1977,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return persister.executeSqlUpdate(updSql, t);
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
persister.addBatch(sqlUpdate, transaction);
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
return persister.executeBatch(sqlUpdate, transaction);
}
/**
* Execute the updateSql.
*/
@@ -1,11 +1,12 @@
package io.ebeaninternal.server.core;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebean.Update;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import java.io.Serializable;
@@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private static final long serialVersionUID = -6493829438421253102L;
private transient final EbeanServer server;
private transient final SpiEbeanServer server;
/**
* The parameters used to bind to the sql.
@@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private Object generatedKey;
/**
* Set when batching explicitly used.
*/
private boolean batched;
/**
* Transaction used for addBatch() executeBatch() processing.
*/
private transient SpiTransaction transaction;
/**
* Create with server sql and bindParams object.
* <p>
@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* same time.
* </p>
*/
public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) {
this.server = server;
this.sql = sql;
this.bindParams = bindParams;
@@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* Create with a specific server. This means you can use the
* SqlUpdate.execute() method.
*/
public DefaultSqlUpdate(EbeanServer server, String sql) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql) {
this(server, sql, new BindParams());
}
@@ -111,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
@Override
public int execute() {
if (server != null) {
if (batched) {
server.executeBatch(this, transaction);
return -1;
}
return server.execute(this);
} else {
// Hopefully this doesn't catch anyone out...
@@ -118,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public int[] executeBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (!batched) {
throw new IllegalStateException("No prior addBatch() called?");
}
return server.executeBatch(this, transaction);
}
@Override
public void addBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (transaction == null) {
transaction = server.currentServerTransaction();
if (transaction == null) {
throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()");
}
}
batched = true;
server.addBatch(this, transaction);
}
@Override
public Object getGeneratedKey() {
return generatedKey;
@@ -20,6 +20,9 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
@@ -44,6 +47,8 @@ import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import io.ebeaninternal.server.logger.DLogManager;
import io.ebeaninternal.server.logger.DLoggerFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
@@ -134,16 +139,19 @@ public class InternalConfiguration {
private final MultiValueBind multiValueBind;
private final SpiLogManager logManager;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.serverConfig = serverConfig;
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
this.jsonFactory = serverConfig.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.cacheManager = cacheManager;
this.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
this.databasePlatform = serverConfig.getDatabasePlatform();
@@ -169,6 +177,20 @@ public class InternalConfiguration {
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = serverConfig.service(SpiLoggerFactory.class);
if (loggerFactory == null) {
loggerFactory = new DLoggerFactory();
}
SpiLogger sql = loggerFactory.create("io.ebean.SQL");
SpiLogger sum = loggerFactory.create("io.ebean.SUM");
SpiLogger txn = loggerFactory.create("io.ebean.TXN");
return new DLogManager(sql, sum, txn);
}
/**
* Create and return the ExpressionFactory based on configuration and database platform.
*/
@@ -262,9 +284,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -378,7 +400,7 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -511,4 +533,8 @@ public class InternalConfiguration {
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
public SpiLogManager getLogManager() {
return logManager;
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.core;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
@@ -27,15 +26,17 @@ public final class PersistRequestUpdateSql extends PersistRequest {
private String description;
private boolean addBatch;
/**
* Create.
*/
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate,
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) sqlUpdate;
this.updateSql = sqlUpdate;
updateSql.reset();
}
@@ -44,11 +45,24 @@ public final class PersistRequestUpdateSql extends PersistRequest {
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
}
/**
* Add this statement to JDBC batch for later execution.
*/
public int addBatch() {
this.addBatch = true;
return executeOrQueue();
}
@Override
public int executeNow() {
return persistExecute.executeSqlUpdate(this);
}
@Override
public boolean isBatchThisRequest() {
return addBatch || super.isBatchThisRequest();
}
@Override
public int executeOrQueue() {
return executeStatement();
@@ -8,6 +8,7 @@ import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -91,4 +92,14 @@ public interface Persister {
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
/**
* Add the statement to JDBC batch for later execution via executeBatch.
*/
void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
/**
* Execute the associated batched statement.
*/
int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
}
@@ -152,6 +152,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
private final IdType idType;
/**
* Set when Id property is marked with GeneratedValue annotation.
*/
private final boolean idGeneratedValue;
private final boolean idTypePlatformDefault;
private final PlatformIdGenerator idGenerator;
@@ -450,6 +455,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.defaultSelectClause = deploy.getDefaultSelectClause();
this.idType = deploy.getIdType();
this.idGeneratedValue = deploy.isIdGeneratedValue();
this.idTypePlatformDefault = deploy.isIdTypePlatformDefault();
this.idGenerator = deploy.getIdGenerator();
this.sequenceName = deploy.getSequenceName();
@@ -631,9 +637,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
public void setEbeanServer(SpiEbeanServer ebeanServer) {
this.ebeanServer = ebeanServer;
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
for (BeanPropertyAssocMany<?> assocMany : propertiesMany) {
// used for creating lazy loading lists etc
aPropertiesMany.setLoader(ebeanServer);
assocMany.setEbeanServer(ebeanServer);
}
}
@@ -1922,6 +1928,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return createEntityBean();
}
/**
* We actually need to do a query because we don't know the type without the discriminator
* value, just select the id property and discriminator column (auto added)
*/
private T findReferenceBean(Object id, PersistenceContext pc) {
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
query.setPersistenceContext(pc);
return query
// .select(getIdProperty().getName())
// we do not select the id because we
// probably have to load the entire bean
.setId(id).findOne();
}
/**
* Create a reference bean based on the id.
*/
@@ -1941,6 +1961,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
@@ -1974,10 +1998,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
return query.select(getIdProperty().getName()).setId(id).findOne();
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
@@ -2984,6 +3005,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return idType;
}
/**
* Return true if the Id value is marked as a <code>@GeneratedValue</code>.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Return true if the identity is the platform default (not explicitly set).
*/
@@ -1369,9 +1369,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setIdType(IdType.EXTERNAL);
return;
}
// use the default. IDENTITY or SEQUENCE.
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
if (desc.isIdGeneratedValue() || serverConfig.isIdGeneratorAutomatic()) {
// use IDENTITY or SEQUENCE based on platform
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
} else {
// externally/application supplied Id values
desc.setIdType(IdType.EXTERNAL);
return;
}
}
if (desc.getBaseTable() == null) {
@@ -1387,21 +1393,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return;
}
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (IdType.SEQUENCE == desc.getIdType()) {
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
@@ -78,6 +78,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
return false;
}
@Override
public Object localEncrypt(Object value) {
throw new IllegalArgumentException("Should not get here?");
}
@Override
public boolean containsFormulaWithJoin() {
return false;
@@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.LocalEncryptedType;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
import io.ebeaninternal.server.type.ScalarTypeEnum;
@@ -1246,6 +1247,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return dbBind;
}
@Override
public Object localEncrypt(Object value) {
return ((LocalEncryptedType)scalarType).localEncrypt(value);
}
/**
* Returns true if DB encrypted.
*/
@@ -65,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
final TableJoin tableJoin;
final PropertyForeignKey foreignKey;
/**
* The type of the joined bean.
*/
@@ -90,6 +92,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
public BeanPropertyAssoc(BeanDescriptor<?> descriptor, DeployBeanPropertyAssoc<T> deploy) {
super(descriptor, deploy);
this.foreignKey = deploy.getForeignKey();
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
@@ -128,6 +131,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
return fetchPreference;
}
/**
* Return the extra configuration for the foreign key.
*/
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Create a ElPropertyValue for a *ToOne or *ToMany.
*/
@@ -7,10 +7,10 @@ import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -50,6 +50,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
private final String intersectionPublishTable;
private final String intersectionDraftTable;
private IntersectionTable intersectionTable;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
@@ -352,10 +354,30 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
*/
public void setLoader(BeanCollectionLoader loader) {
public void setEbeanServer(SpiEbeanServer server) {
if (help != null) {
help.setLoader(loader);
help.setLoader(server);
}
if (manyToMany) {
intersectionTable = initIntersectionTable();
}
}
private IntersectionTable initIntersectionTable() {
IntersectionBuilder row = new IntersectionBuilder(intersectionPublishTable, intersectionDraftTable);
for (ExportedProperty exportedProperty : exportedProperties) {
row.addColumn(exportedProperty.getForeignDbColumn());
}
importedId.buildImport(row);
return row.build();
}
/**
* Return the intersection table helper.
*/
public IntersectionTable intersectionTable() {
return intersectionTable;
}
/**
@@ -755,6 +777,20 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable);
}
/**
* Bind the two side of a many to many to the given SqlUpdate.
*/
public void intersectionBind(SqlUpdate sql, EntityBean parentBean, EntityBean other) {
if (embeddedExportedProperties) {
BeanProperty idProp = descriptor.getIdProperty();
parentBean = (EntityBean) idProp.getValue(parentBean);
}
for (ExportedProperty exportedProperty : exportedProperties) {
sql.setNextParameter(exportedProperty.getValue(parentBean));
}
importedId.bindImport(sql, other);
}
private void buildExport(IntersectionRow row, EntityBean parentBean) {
if (embeddedExportedProperties) {
@@ -46,15 +46,16 @@ class BeanPropertyAssocManyJsonHelp {
if (JsonToken.VALUE_NULL == event) {
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array ");
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
}
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
@@ -44,8 +44,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private final boolean primaryKeyExport;
private final PropertyForeignKey foreignKey;
private AssocOneHelp localHelp;
protected final BeanProperty[] embeddedProps;
@@ -73,8 +71,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
DeployBeanPropertyAssocOne<T> deploy) {
super(descriptor, deploy);
foreignKey = deploy.getForeignKey();
primaryKeyExport = deploy.isPrimaryKeyExport();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
@@ -323,10 +319,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public boolean hasForeignKey() {
return foreignKey == null || !foreignKey.isNoConstraint();
}
@@ -0,0 +1,85 @@
package io.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to build IntersectionTable.
*/
public class IntersectionBuilder {
private final String publishTable;
private final String draftTable;
private final List<String> columns = new ArrayList<>();
IntersectionBuilder(String publishTable, String draftTable) {
this.publishTable = publishTable;
this.draftTable = draftTable;
}
public void addColumn(String column) {
columns.add(column);
}
public IntersectionTable build() {
String insertSql = insertSql(publishTable);
String deleteSql = deleteSql(publishTable);
String draftInsertSql;
String draftDeleteSql;
if (publishTable.equals(draftTable)) {
draftInsertSql = insertSql;
draftDeleteSql = deleteSql;
} else {
draftInsertSql = insertSql(draftTable);
draftDeleteSql = deleteSql(draftTable);
}
return new IntersectionTable(insertSql, deleteSql, draftInsertSql, draftDeleteSql);
}
private String insertSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("insert into ").append(tableName).append(" (");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(", ");
}
sb.append(column);
}
sb.append(") values (");
for (int i = 0; i < count; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
private String deleteSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(tableName);
sb.append(" where ");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(" and ");
}
sb.append(column);
sb.append(" = ?");
}
return sb.toString();
}
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.IdInExpression;
@@ -22,12 +22,12 @@ public class IntersectionRow {
private List<Object> excludeIds;
private BeanDescriptor<?> excludeDescriptor;
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
this.tableName = tableName;
this.targetDescriptor = targetDescriptor;
}
public IntersectionRow(String tableName) {
IntersectionRow(String tableName) {
this.tableName = tableName;
this.targetDescriptor = null;
}
@@ -35,7 +35,7 @@ public class IntersectionRow {
/**
* Set Id's to exclude. This is for deleting non-attached detail Id's.
*/
public void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
this.excludeIds = excludeIds;
this.excludeDescriptor = excludeDescriptor;
}
@@ -44,7 +44,7 @@ public class IntersectionRow {
values.put(key, value);
}
public SqlUpdate createInsert(EbeanServer server) {
public SqlUpdate createInsert(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -72,7 +72,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) {
BindParams bindParams = new BindParams();
@@ -107,7 +107,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDeleteChildren(EbeanServer server) {
public SqlUpdate createDeleteChildren(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
public class IntersectionTable {
private final String insertSql;
private final String deleteSql;
private final String draftInsertSql;
private final String draftDeleteSql;
IntersectionTable(String insertSql, String deleteSql, String draftInsertSql, String draftDeleteSql) {
this.insertSql = insertSql;
this.deleteSql = deleteSql;
this.draftInsertSql = draftInsertSql;
this.draftDeleteSql = draftDeleteSql;
}
/**
* Return a SqlUpdate for inserting into the intersection table.
*/
public SqlUpdate insert(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftInsertSql : insertSql);
}
/**
* Return a SqlUpdate for deleting from the intersection table.
*/
public SqlUpdate delete(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftDeleteSql : deleteSql);
}
}
@@ -4,6 +4,7 @@ import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -66,4 +67,14 @@ public interface ImportedId {
* Return the set importedId clause.
*/
String importedIdClause();
/**
* Add DB columns to the intersection builder.
*/
void buildImport(IntersectionBuilder row);
/**
* Bind values to the intersection SqlUpdate.
*/
void bindImport(SqlUpdate sql, EntityBean other);
}
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -130,15 +131,31 @@ public class ImportedIdEmbedded implements ImportedId {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
String msg = "Foreign Key value null?";
throw new PersistenceException(msg);
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
row.put(anImported.localDbColumn, scalarValue);
}
}
@Override
public void buildImport(IntersectionBuilder row) {
for (ImportedIdSimple importedScalar : imported) {
row.addColumn(importedScalar.localDbColumn);
}
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
sql.setNextParameter(scalarValue);
}
}
/**
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanFkeyProperty;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -98,6 +99,20 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
return foreignProperty.getValue(bean);
}
@Override
public void buildImport(IntersectionBuilder row) {
row.addColumn(localDbColumn);
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
Object value = getIdValue(other);
if (value == null) {
throw new PersistenceException("Foreign Key value null?");
}
sql.setNextParameter(value);
}
@Override
public void buildImport(IntersectionRow row, EntityBean other) {
@@ -106,6 +106,11 @@ public class DeployBeanDescriptor<T> {
private PlatformIdGenerator idGenerator;
/**
* Set true when explicit auto generated Id.
*/
private boolean idGeneratedValue;
/**
* The database sequence name (optional).
*/
@@ -848,6 +853,20 @@ public class DeployBeanDescriptor<T> {
}
}
/**
* Return true for automatic Id generation strategy.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Set when GeneratedValue explicitly mapped on Id property.
*/
public void setIdGeneratedValue() {
this.idGeneratedValue = true;
}
/**
* Assign the standard UUID generator.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
@@ -16,7 +17,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
/**
* Persist settings.
*/
protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
/**
* The join table information.
@@ -42,6 +43,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
private int fetchPreference = 1000;
private PropertyForeignKey foreignKey;
/**
* Construct the property.
*/
@@ -117,6 +120,13 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
return cascadeInfo;
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Return the mappedBy deployment attribute.
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.query.SqlJoinType;
import javax.persistence.CascadeType;
@@ -22,8 +21,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
private String columnPrefix;
private PropertyForeignKey foreignKey;
private boolean orphanRemoval;
/**
@@ -148,14 +145,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
}
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public void setOrphanRemoval(boolean orphanRemoval) {
this.orphanRemoval = orphanRemoval;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.PrivateOwned;
@@ -12,6 +13,7 @@ import io.ebean.util.StringHelper;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
@@ -95,6 +97,12 @@ class AnnotationAssocManys extends AnnotationParser {
readElementCollection(prop, elementCollection);
}
// for ManyToMany typically to disable foreign keys from intersection table
DbForeignKey dbForeignKey = get(prop, DbForeignKey.class);
if (dbForeignKey != null){
prop.setForeignKey(new PropertyForeignKey(dbForeignKey));
}
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory();
}
@@ -184,7 +184,7 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation?";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
@@ -322,7 +322,7 @@ public class AnnotationFields extends AnnotationParser {
prop.setDbLength(length.value());
}
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
if (nonNull != null) {
prop.setNullable(false);
}
@@ -389,7 +389,7 @@ public class AnnotationFields extends AnnotationParser {
Set<DbMigration> dbMigration = getAll(prop, DbMigration.class);
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
}
private void addIndex(DeployBeanProperty prop, Index index) {
@@ -515,6 +515,7 @@ public class AnnotationFields extends AnnotationParser {
private void readGenValue(GeneratedValue gen, DeployBeanProperty prop) {
descriptor.setIdGeneratedValue();
String genName = gen.generator();
SequenceGenerator sequenceGenerator = find(prop, SequenceGenerator.class);
@@ -174,6 +174,11 @@ public class ElPropertyChain implements ElPropertyValue {
return lastElPropertyValue.isLocalEncrypted();
}
@Override
public Object localEncrypt(Object value) {
return lastElPropertyValue.localEncrypt(value);
}
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
return lastElPropertyValue.getAssocIsEmpty(request, path);
@@ -52,6 +52,11 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
*/
boolean isDbEncrypted();
/**
* Encrypt the input value return the encrypted value.
*/
Object localEncrypt(Object value);
/**
* Return the value ensuring objects prior to the top scalar property are
* automatically populated.
@@ -84,10 +84,11 @@ public class SimpleExpression extends AbstractValueExpression {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindEncryptKey(encryptKey);
} else if (prop.isLocalEncrypted()) {
Object bindVal = prop.localEncrypt(value());
request.addBindEncryptKey(bindVal);
return;
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
// prop.getBeanProperty().getScalarType();
}
request.addBindValue(value());
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
public class DLogManager implements SpiLogManager {
private final SpiLogger sql;
private final SpiLogger summary;
private final SpiLogger txn;
public DLogManager(SpiLogger sql, SpiLogger summary, SpiLogger txn) {
this.sql = sql;
this.summary = summary;
this.txn = txn;
}
@Override
public SpiLogger txn() {
return txn;
}
@Override
public SpiLogger sql() {
return sql;
}
@Override
public SpiLogger sum() {
return summary;
}
}
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import org.slf4j.LoggerFactory;
/**
* Default SpiLoggerFactory implementation.
*/
public class DLoggerFactory implements SpiLoggerFactory {
/**
* Just use a standard slf4j Logger.
*/
@Override
public SpiLogger create(String name) {
return new DSpiLogger(LoggerFactory.getLogger(name));
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import org.slf4j.Logger;
public class DSpiLogger implements SpiLogger {
private final Logger logger;
public DSpiLogger(Logger logger) {
this.logger = logger;
}
@Override
public boolean isDebug() {
return logger.isDebugEnabled();
}
@Override
public boolean isTrace() {
return logger.isTraceEnabled();
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void trace(String msg) {
logger.trace(msg);
}
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -357,4 +358,11 @@ public final class BatchControl {
private BatchedBeanHolder[] getBeanHolderArray() {
return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]);
}
/**
* Execute a batched statement.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
return pstmtHolder.execute(key, getGeneratedKeys);
}
}
@@ -38,6 +38,8 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
private long profileStart;
private int[] results;
/**
* Create with a given statement.
*/
@@ -116,7 +118,7 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
private void executeAndCheckRowCounts() throws SQLException {
int[] results = pstmt.executeBatch();
results = pstmt.executeBatch();
if (results.length != list.size()) {
String s = "results array error " + results.length + " " + list.size();
throw new SQLException(s);
@@ -140,4 +142,10 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
}
}
/**
* Return the execution results (row counts).
*/
public int[] getResults() {
return results;
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.persist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedHashMap;
@@ -73,6 +74,19 @@ public class BatchedPstmtHolder {
return stmtMap.isEmpty();
}
/**
* Execute one of the batched statements returning the row counts.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
BatchedPstmt batchedPstmt = stmtMap.remove(key);
if (batchedPstmt == null) {
throw new PersistenceException("No batched statement found for key " + key);
}
batchedPstmt.executeBatch(getGeneratedKeys);
return batchedPstmt.getResults();
}
/**
* Execute all batched PreparedStatements.
*
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.persist;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
@@ -48,7 +48,7 @@ public class Binder {
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
public Binder(TypeManager typeManager, SpiLogManager logManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) {
this.typeManager = typeManager;
@@ -57,12 +57,7 @@ public class Binder {
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
this.multiValueBind = multiValueBind;
this.enableBindLog = enableBindLog();
}
private boolean enableBindLog() {
return TransactionManager.SQL_LOGGER.isDebugEnabled()
|| TransactionManager.SUM_LOGGER.isDebugEnabled();
this.enableBindLog = logManager.sql().isDebug();
}
/**
@@ -13,6 +13,7 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanPersistController;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdate;
import io.ebeaninternal.server.core.Message;
@@ -34,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -124,13 +126,28 @@ public final class DefaultPersister implements Persister {
}
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
new PersistRequestUpdateSql(server, sqlUpdate, transaction, persistExecute).addBatch();
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
BatchControl batchControl = transaction.getBatchControl();
try {
return batchControl.execute(sqlUpdate.getSql(), sqlUpdate.isGetGeneratedKeys());
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* Execute the updateSql.
*/
@Override
public int executeSqlUpdate(SqlUpdate updSql, Transaction t) {
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
return executeOrQueue(new PersistRequestUpdateSql(server, (SpiSqlUpdate) updSql, (SpiTransaction) t, persistExecute));
}
/**
@@ -26,6 +26,14 @@ class MergeContext {
this.clientGeneratedIds = clientGeneratedIds;
}
public SpiEbeanServer getServer() {
return server;
}
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Add to the list of beans to delete.
*/
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*/
class MergeHandler {
private final Pattern PATH_SPLIT = Pattern.compile("\\.");
private static final Pattern PATH_SPLIT = Pattern.compile("\\.");
private final SpiEbeanServer server;
private final BeanDescriptor<?> desc;
@@ -51,7 +51,7 @@ class MergeHandler {
List<EntityBean> merge() {
Set<String> paths = options.paths();
if (paths.isEmpty() && !options.isClientGeneratedIds()) {
if (desc.isIdGeneratedValue() && paths.isEmpty() && !options.isClientGeneratedIds()) {
// just do a single insert or update based on Id value present
Object id = desc.getId(bean);
if (id != null) {
@@ -139,7 +139,12 @@ class MergeHandler {
throw new PersistenceException("merge path [" + path + "] is not a ToMany or ToOne property of " + targetDesc.getFullName());
}
if (prop instanceof BeanPropertyAssocMany<?>) {
return new MergeNodeAssocMany(fullPath, (BeanPropertyAssocMany<?>) prop);
BeanPropertyAssocMany<?> assocMany = (BeanPropertyAssocMany<?>) prop;
if (assocMany.isManyToMany()) {
return new MergeNodeAssocManyToMany(fullPath, assocMany);
} else {
return new MergeNodeAssocOneToMany(fullPath, assocMany);
}
} else {
return new MergeNodeAssocOne(fullPath, (BeanPropertyAssocOne<?>) prop);
}
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -50,6 +52,22 @@ abstract class MergeNode {
return null;
}
/**
* Return the outline beans as a map keyed by Id values.
*/
Map<Object, EntityBean> toMap(Collection outlines) {
Map<Object, EntityBean> outlineMap = new HashMap<>();
if (outlines != null) {
for (Object out : outlines) {
EntityBean outlineBean = (EntityBean) out;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineMap.put(outlineId, outlineBean);
}
}
return outlineMap;
}
/**
* Add to the query to fetch the Ids values for the foreign keys basically.
*/
@@ -0,0 +1,82 @@
package io.ebeaninternal.server.persist;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.IntersectionTable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Node for processing merge on ManyToMany properties.
*/
class MergeNodeAssocManyToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocManyToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
public void merge(MergeRequest request) {
EntityBean parentBean = request.getBean();
Collection beans = many.getRawCollection(parentBean);
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = toMap(outlines);
List<EntityBean> additions = new ArrayList<>();
if (beans != null) {
for (Object bean : beans) {
EntityBean entityBean = (EntityBean) bean;
Object beanId = targetDescriptor.getId(entityBean);
if (beanId != null) {
if (outlineIds.remove(beanId) == null) {
additions.add(entityBean);
}
}
}
}
// any remaining are considered deletes
List<EntityBean> deletions = new ArrayList<>(outlineIds.values());
SpiEbeanServer server = request.getServer();
SpiTransaction transaction = request.getTransaction();
IntersectionTable intersectionTable = many.intersectionTable();
if (!deletions.isEmpty()) {
transaction.flushBatch();
SqlUpdate delete = intersectionTable.delete(server, false);
for (EntityBean deletion : deletions) {
many.intersectionBind(delete, parentBean, deletion);
delete.addBatch();
}
delete.execute();
}
if (!additions.isEmpty()) {
transaction.flushBatch();
SqlUpdate insert = intersectionTable.insert(server, false);
for (EntityBean addition : additions) {
many.intersectionBind(insert, parentBean, addition);
insert.addBatch();
}
insert.execute();
}
many.resetMany(parentBean);
}
}
@@ -10,11 +10,11 @@ import java.util.Map;
/**
* Node for processing merge on ToMany properties.
*/
class MergeNodeAssocMany extends MergeNode {
class MergeNodeAssocOneToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocMany(String fullPath, BeanPropertyAssocMany<?> property) {
MergeNodeAssocOneToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
@@ -24,14 +24,7 @@ class MergeNodeAssocMany extends MergeNode {
Collection beans = many.getRawCollection(request.getBean());
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = new HashMap<>();
if (outlines != null) {
for (Object outline : outlines) {
EntityBean outlineBean = (EntityBean) outline;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineIds.put(outlineId, outlineBean);
}
}
Map<Object, EntityBean> outlineIds = toMap(outlines);
if (beans != null) {
for (Object bean : beans) {
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.persist;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
/**
* Request object used for processing the merge.
@@ -18,6 +20,20 @@ class MergeRequest {
this.outline = outline;
}
/**
* Return the associated server.
*/
SpiEbeanServer getServer() {
return context.getServer();
}
/**
* Return the associated transaction.
*/
public SpiTransaction getTransaction() {
return context.getTransaction();
}
/**
* Create a sub request with the given beans (to cascade the processing).
*/
@@ -154,9 +154,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
*/
protected void logSql(String sql) {
if (logLevelSql) {
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
}
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
transaction.logSql(sql);
}
}
@@ -187,7 +185,10 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
@Override
public void bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException {
if (logLevelSql) {
bindLog.append(logPlaceHolder).append(" ");
if (bindLog.length() > 0) {
bindLog.append(",");
}
bindLog.append(logPlaceHolder);
}
dataBind.setObject(value, sqlType);
}
@@ -19,7 +19,6 @@ import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,9 +81,7 @@ public class CQueryEngine {
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
}
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
request.logSql(logSql);
}
@@ -151,11 +148,7 @@ public class CQueryEngine {
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
String logSql = sql;
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
request.logSql(logSql);
request.logSql(Str.add(sql, "; --bind(", bindLog, ")"));
}
/**
@@ -458,9 +451,7 @@ public class CQueryEngine {
private void logSql(CQuery<?> query) {
String sql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
}
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
query.getTransaction().logSql(sql);
}
@@ -1276,8 +1276,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public boolean isBeanCacheReload() {
return CacheMode.PUT == useBeanCache;
public boolean isForceHitDatabase() {
return forUpdate != null || CacheMode.PUT == useBeanCache;
}
@Override
@@ -43,6 +43,9 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private final TransactionManager manager;
private final boolean logSql;
private final boolean logSummary;
/**
* The status of the transaction.
*/
@@ -70,6 +73,8 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
this.manager = manager;
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.active = true;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
@@ -441,22 +446,22 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(msg);
manager.log().sql().debug(msg);
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(msg);
manager.log().sum().debug(msg);
}
/**
@@ -55,6 +55,9 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
protected final String id;
private final boolean logSql;
private final boolean logSummary;
/**
* The user defined label to group execution statistics.
*/
@@ -200,11 +203,15 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.startNanos = System.nanoTime();
if (manager == null) {
this.logSql = false;
this.logSummary = false;
this.skipCacheAfterWrite = true;
this.batchMode = PersistBatch.NONE;
this.batchOnCascadeMode = PersistBatch.NONE;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
this.batchMode = manager.getPersistBatch();
this.batchOnCascadeMode = manager.getPersistBatchOnCascade();
@@ -861,22 +868,22 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sql().debug(Str.add(logPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sum().debug(Str.add(logPrefix, msg));
}
/**
@@ -21,6 +21,7 @@ class SavepointTransaction extends SpiTransactionProxy {
private final Savepoint savepoint;
private final Connection connection;
private final String logPrefix;
private final String spPrefix;
private boolean rollbackOnly;
private int state;
@@ -31,7 +32,8 @@ class SavepointTransaction extends SpiTransactionProxy {
this.connection = transaction.getInternalConnection();
this.savepoint = connection.setSavepoint();
int savepointId = savepoint.getSavepointId();
this.logPrefix = "txn[" + transaction.getId() + "-sp" + savepointId + "] ";
this.spPrefix = "sp[" + savepointId + "] ";
this.logPrefix = transaction.getLogPrefix() + spPrefix;
}
@Override
@@ -41,12 +43,12 @@ class SavepointTransaction extends SpiTransactionProxy {
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
transaction.logSql(Str.add(spPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
transaction.logSummary(Str.add(spPrefix, msg));
}
@Override
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiTransaction;
import org.slf4j.Logger;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -14,8 +13,6 @@ import java.util.concurrent.atomic.AtomicLong;
*/
abstract class TransactionFactory {
static final Logger TXN_LOGGER = TransactionManager.TXN_LOGGER;
final AtomicLong counter = new AtomicLong(1000);
final TransactionManager manager;
@@ -51,8 +48,8 @@ abstract class TransactionFactory {
}
}
if (explicit && TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(t.getLogPrefix() + "Begin");
if (explicit && manager.log().txn().isTrace()) {
manager.log().txn().trace(t.getLogPrefix() + "Begin");
}
return t;
@@ -15,6 +15,8 @@ import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
@@ -53,12 +55,6 @@ public class TransactionManager implements SpiTransactionManager {
public static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
public static final Logger SQL_LOGGER = LoggerFactory.getLogger("io.ebean.SQL");
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("io.ebean.SUM");
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("io.ebean.TXN");
protected final BeanDescriptorManager beanDescriptorManager;
/**
@@ -128,6 +124,9 @@ public class TransactionManager implements SpiTransactionManager {
private final TransactionFactory transactionFactory;
private final SpiLogManager logManager;
private final SpiLogger txnLogger;
private final DatabasePlatform databasePlatform;
private final SpiProfileHandler profileHandler;
@@ -142,6 +141,8 @@ public class TransactionManager implements SpiTransactionManager {
*/
public TransactionManager(TransactionManagerOptions options) {
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
@@ -355,12 +356,12 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
if (txnLogger.isDebug()) {
String msg = transaction.getLogPrefix() + "Rollback";
if (cause != null) {
msg += " error: " + formatThrowable(cause);
}
TXN_LOGGER.debug(msg);
txnLogger.debug(msg);
}
} catch (Exception ex) {
@@ -374,8 +375,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfQueryOnly(SpiTransaction transaction) {
// Nothing that interesting here
if (TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(transaction.getLogPrefix() + "Commit - query only");
if (txnLogger.isTrace()) {
txnLogger.trace(transaction.getLogPrefix() + "Commit - query only");
}
}
@@ -409,8 +410,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfCommit(SpiTransaction transaction) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit");
if (txnLogger.isDebug()) {
txnLogger.debug(transaction.getLogPrefix() + "Commit");
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
@@ -702,4 +703,16 @@ public class TransactionManager implements SpiTransactionManager {
throw new RuntimeException("Should never get here?");
}
}
public SpiLogManager log() {
return logManager;
}
public boolean isLogSql() {
return logManager.sql().isDebug();
}
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -22,10 +23,11 @@ public class TransactionManagerOptions {
final DataSourceSupplier dataSourceSupplier;
final SpiProfileHandler profileHandler;
final TransactionScopeManager scopeManager;
final SpiLogManager logManager;
public TransactionManagerOptions(boolean notifyL2CacheInForeground, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler) {
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler, SpiLogManager logManager) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
@@ -36,6 +38,7 @@ public class TransactionManagerOptions {
this.descMgr = descMgr;
this.dataSourceSupplier = dataSourceSupplier;
this.profileHandler = profileHandler;
this.logManager = logManager;
}
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.server.type;
/**
* Scalar type that wraps a local/client side encrypted value.
*/
public interface LocalEncryptedType {
/**
* Encrypt and return the un-encrypted value.
*/
Object localEncrypt(Object value);
}
@@ -9,7 +9,7 @@ import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T>, LocalEncryptedType {
private final ScalarType<T> wrapped;
@@ -23,6 +23,12 @@ public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
this.dataEncryptSupport = dataEncryptSupport;
}
@Override
public Object localEncrypt(Object value) {
String formatValue = wrapped.format(value);
return dataEncryptSupport.encryptObject(formatValue);
}
@Override
public long asVersion(T value) {
throw new RuntimeException("not supported");
@@ -66,6 +66,7 @@ public class ServerConfigTest {
props.setProperty("idType", "SEQUENCE");
props.setProperty("mappingLocations", "classpath:/foo;bar");
props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention");
props.setProperty("idGeneratorAutomatic", "true");
serverConfig.loadFromProperties(props);
@@ -74,6 +75,8 @@ public class ServerConfigTest {
assertTrue(serverConfig.isNotifyL2CacheInForeground());
assertTrue(serverConfig.isDbOffline());
assertTrue(serverConfig.isAutoReadOnlyDataSource());
assertTrue(serverConfig.isIdGeneratorAutomatic());
assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
assertEquals(IdType.SEQUENCE, serverConfig.getIdType());
@@ -108,4 +111,14 @@ public class ServerConfigTest {
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
}
@Test
public void test_defaults() {
ServerConfig serverConfig = new ServerConfig();
assertTrue(serverConfig.isIdGeneratorAutomatic());
serverConfig.setIdGeneratorAutomatic(false);
assertFalse(serverConfig.isIdGeneratorAutomatic());
}
}
@@ -77,6 +77,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
this.name = name;
}
@Override
public SpiLogManager log() {
return null;
}
@Override
public void shutdownManaged() {
@@ -237,6 +242,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return 0;
}
@Override
public void merge(Object bean) {
}
@Override
public void merge(Object bean, MergeOptions options) {
@@ -800,6 +810,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return 0;
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
}
@Override
public int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction) {
return new int[0];
}
@Override
public int execute(CallableSql callableSql, Transaction t) {
return 0;
@@ -1,11 +1,20 @@
package io.ebeaninternal.server.deploy;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.bean.EntityBean;
import io.ebean.plugin.Property;
import org.junit.Test;
import org.tests.model.basic.Animal;
import org.tests.model.basic.AnimalShelter;
import org.tests.model.basic.Cat;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Country;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Dog;
import org.tests.model.basic.Order;
import org.tests.model.bridge.BSite;
import org.tests.model.bridge.BUser;
import java.util.Collection;
@@ -13,7 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanDescriptorTest extends BaseTestCase {
BeanDescriptor<Customer> customerDesc = spiEbeanServer().getBeanDescriptor(Customer.class);
private BeanDescriptor<Customer> customerDesc = spiEbeanServer().getBeanDescriptor(Customer.class);
@Test
public void createReference() {
@@ -47,6 +56,29 @@ public class BeanDescriptorTest extends BaseTestCase {
assertThat(server().getBeanState(bean).isDisableLazyLoad()).isTrue();
}
@Test
public void createReference_with_inheritance() {
Cat cat = new Cat();
cat.setName("Puss");
Ebean.save(cat);
Dog dog = new Dog();
dog.setRegistrationNumber("DOGGIE");
Ebean.save(dog);
AnimalShelter shelter = new AnimalShelter();
shelter.setName("My Animal Shelter");
shelter.getAnimals().add(cat);
shelter.getAnimals().add(dog);
Ebean.save(shelter);
BeanDescriptor<Animal> animalDesc = spiEbeanServer().getBeanDescriptor(Animal.class);
Animal bean = animalDesc.createReference(Boolean.FALSE, false, dog.getId(), null);
assertThat(bean.getId()).isEqualTo(dog.getId());
}
@Test
public void allProperties() {
@@ -70,4 +102,37 @@ public class BeanDescriptorTest extends BaseTestCase {
assertThat(to.getName()).isEqualTo("rob");
}
@Test
public void isIdTypeExternal_when_externalId() {
BeanDescriptor<Country> countryDesc = spiEbeanServer().getBeanDescriptor(Country.class);
assertThat(countryDesc.isIdGeneratedValue()).isFalse();
}
@Test
public void isIdTypeExternal_when_platformGenerator_noGeneratedValueAnnotation() {
assertThat(customerDesc.isIdGeneratedValue()).isFalse();
}
@Test
public void isIdTypeExternal_when_explicitGeneratedValue() {
BeanDescriptor<Contact> desc = spiEbeanServer().getBeanDescriptor(Contact.class);
assertThat(desc.isIdGeneratedValue()).isTrue();
}
@Test
public void isIdTypeExternal_when_uuidGenerator_and_generatedValue() {
BeanDescriptor<BSite> desc = spiEbeanServer().getBeanDescriptor(BSite.class);
assertThat(desc.isIdGeneratedValue()).isTrue();
}
@Test
public void isIdTypeExternal_when_uuidGenerator_and_noGeneratedValue() {
BeanDescriptor<BUser> desc = spiEbeanServer().getBeanDescriptor(BUser.class);
assertThat(desc.isIdGeneratedValue()).isFalse();
}
}
@@ -0,0 +1,64 @@
package io.ebeantest;
import io.ebeaninternal.api.SpiLogger;
import java.util.ArrayList;
import java.util.List;
/**
* Capture the log messages (executed SQL) for testing.
*/
class CaptureLogger implements SpiLogger {
private final SpiLogger wrapped;
private List<String> messages = new ArrayList<>();
private boolean active;
CaptureLogger(SpiLogger wrapped) {
this.wrapped = wrapped;
}
@Override
public boolean isDebug() {
return true;
}
@Override
public boolean isTrace() {
return true;
}
@Override
public void debug(String msg) {
if (active) {
messages.add(msg);
}
wrapped.debug(msg);
}
@Override
public void trace(String msg) {
if (active) {
messages.add(msg);
}
wrapped.trace(msg);
}
List<String> start() {
this.active = true;
return collect();
}
List<String> stop() {
this.active = false;
return collect();
}
List<String> collect() {
List<String> response = messages;
messages = new ArrayList<>();
return response;
}
}
@@ -0,0 +1,22 @@
package io.ebeantest;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import io.ebeaninternal.server.logger.DSpiLogger;
import org.slf4j.LoggerFactory;
/**
* Create a logger that captures the SQL and register it for later access in tests.
*/
public class CapturingLoggerFactory implements SpiLoggerFactory {
@Override
public SpiLogger create(String name) {
DSpiLogger logger = new DSpiLogger(LoggerFactory.getLogger(name));
if (name.equals("io.ebean.SQL")) {
return LoggedSql.register(logger);
}
return logger;
}
}
+31
View File
@@ -0,0 +1,31 @@
package io.ebeantest;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.server.logger.DSpiLogger;
import java.util.List;
public class LoggedSql {
private static CaptureLogger sqlLogger;
static SpiLogger register(DSpiLogger logger) {
if (sqlLogger == null) {
sqlLogger = new CaptureLogger(logger);
}
return sqlLogger;
}
public static List<String> start() {
return sqlLogger.start();
}
public static List<String> stop() {
return sqlLogger.stop();
}
public static List<String> collect() {
return sqlLogger.collect();
}
}
@@ -1,13 +1,7 @@
package org.ebeantest;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.slf4j.LoggerFactory;
import io.ebeantest.LoggedSql;
import java.util.ArrayList;
import java.util.List;
/**
@@ -20,28 +14,11 @@ import java.util.List;
*/
public class LoggedSqlCollector {
static Logger logger = (Logger) LoggerFactory.getLogger("io.ebean.SQL");
private static BasicAppender basicAppender = new BasicAppender();
static {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
basicAppender.setContext(lc);
Level level = logger.getEffectiveLevel();
if (level.isGreaterOrEqual(Level.INFO)) {
logger.setAdditive(false);
}
logger.addAppender(basicAppender);
logger.setLevel(Level.TRACE);
}
/**
* Start collection of the logged SQL statements.
*/
public static List<String> start() {
return basicAppender.collectStart();
return LoggedSql.start();
}
/**
@@ -49,54 +26,11 @@ public class LoggedSqlCollector {
* the SQL.
*/
public static List<String> stop() {
return basicAppender.collectEnd();
return LoggedSql.stop();
}
public static List<String> current() {
return basicAppender.collectContinue();
return LoggedSql.collect();
}
private static class BasicAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
List<String> messages = new ArrayList<>();
@Override
protected void append(ILoggingEvent eventObject) {
if (started) {
messages.add(eventObject.getMessage());
}
}
/**
* Start collection.
*/
List<String> collectStart() {
List<String> tempMessages = messages;
messages = new ArrayList<>();
// set started flag
start();
return tempMessages;
}
/**
* End collection.
*/
List<String> collectEnd() {
// set stopped state
stop();
List<String> tempMessages = messages;
messages = new ArrayList<>();
return tempMessages;
}
/**
* Return the collected SQL and continue.
*/
List<String> collectContinue() {
List<String> tempMessages = messages;
messages = new ArrayList<>();
return tempMessages;
}
}
}
@@ -8,8 +8,10 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.EBasic;
import org.tests.model.basic.ResetBasicData;
import java.util.List;
@@ -40,6 +42,42 @@ public class TestQueryForUpdate extends BaseTestCase {
}
}
@Test
@ForPlatform({
Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL
})
public void testForUpdate_when_alreadyInPC() {
EBasic basic = new EBasic("test PC cache");
Ebean.save(basic);
try (Transaction transaction = Ebean.beginTransaction()) {
LoggedSqlCollector.start();
EBasic basic0 = Ebean.find(EBasic.class, basic.getId());
assertThat(basic0).isNotNull();
EBasic basic1 = Ebean.find(EBasic.class)
.setId( basic.getId())
.forUpdate()
.findOne();
assertThat(basic1).isNotNull();
assertThat(basic1).isSameAs(basic0);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
if (isH2() || isPostgres()) {
assertThat(sql.get(0)).contains("from e_basic t0 where t0.id =");
assertThat(sql.get(1)).contains("from e_basic t0 where t0.id =");
assertThat(sql.get(1)).contains("for update");
}
transaction.end();
}
}
@Test
@ForPlatform({
Platform.H2, Platform.POSTGRES, Platform.SQLSERVER, Platform.ORACLE
@@ -0,0 +1,55 @@
package org.tests.basic.encrypt;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Test;
import org.tests.model.basic.EBasicEncryptClient;
import java.time.LocalDate;
import static org.assertj.core.api.Assertions.assertThat;
public class TestEncryptClientSide extends BaseTestCase {
@Test
public void insertUpdate() {
LocalDate today = LocalDate.now();
EBasicEncryptClient bean = new EBasicEncryptClient();
bean.setDescription("hello");
bean.setStatus(EBasicEncryptClient.Status.ONE);
bean.setDob(today);
Ebean.save(bean);
EBasicEncryptClient found = Ebean.find(EBasicEncryptClient.class)
.where()
.eq("description", "hello")
.eq("status", EBasicEncryptClient.Status.ONE)
.eq("dob", today)
.findOne();
assertThat(found).isNotNull();
assertThat(found.getDescription()).isEqualTo("hello");
assertThat(found.getStatus()).isEqualTo(EBasicEncryptClient.Status.ONE);
assertThat(found.getDob()).isEqualTo(today);
found.setDescription("goodbye");
found.setStatus(EBasicEncryptClient.Status.TWO);
Ebean.save(found);
found = Ebean.find(EBasicEncryptClient.class)
.where()
.eq("description", "goodbye")
.eq("status", EBasicEncryptClient.Status.TWO)
.eq("dob", today)
.findOne();
assertThat(found).isNotNull();
assertThat(found.getDescription()).isEqualTo("goodbye");
Ebean.delete(found);
}
}
@@ -0,0 +1,54 @@
package org.tests.ddl;
import io.ebean.annotation.DbForeignKey;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class DfkNoneViaMtoM {
@Id
long id;
String name;
/**
* Have no Foreign key constraints on the intersection table.
*/
@ManyToMany(cascade = CascadeType.ALL)
@DbForeignKey(noConstraint = true)
List<DfkOne> ones = new ArrayList<>();
public DfkNoneViaMtoM(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<DfkOne> getOnes() {
return ones;
}
public void setOnes(List<DfkOne> ones) {
this.ones = ones;
}
}
@@ -48,6 +48,40 @@ public class TestForeignKeyModes extends BaseTestCase {
assertThat(found.getOne()).isNotNull();
}
@Test
public void noneViaManyToMany() {
DfkOne one = Ebean.getReference(DfkOne.class, 999L);
DfkNoneViaMtoM none = new DfkNoneViaMtoM("none2");
none.getOnes().add(one);
// Would normally fail as DfkOne id:999 is not actually in Database
// and with the foreign key constraint on the intersection table the
// insert into the intersection table would fail
Ebean.save(none);
DfkNoneViaMtoM found = Ebean.find(DfkNoneViaMtoM.class)
.setId(none.getId())
.fetch("ones", "id")
.findOne();
assertThat(found).isNotNull();
// but we can't actually fetch it via ORM M2M ... as it joins across
// to DfkOne and that doesn't exist
assertThat(found.getOnes()).isNotNull();
assertThat(found.getOnes()).hasSize(0);
LoggedSqlCollector.start();
Ebean.delete(none);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("delete from dfk_none_via_mto_m_dfk_one where dfk_none_via_mto_m_id = ?");
assertThat(sql.get(1)).contains("delete from dfk_none_via_mto_m where id=?");
}
@Test
public void setNullOnDelete() {
@@ -63,7 +97,6 @@ public class TestForeignKeyModes extends BaseTestCase {
DfkSetNull found = Ebean.find(DfkSetNull.class, other.getId());
assertThat(found).isNotNull();
assertThat(found.getOne()).isNull();
}
@Test
@@ -0,0 +1,49 @@
package org.tests.inheritance;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import javax.validation.constraints.NotNull;
import io.ebean.annotation.Index;
/**
* Model class to reference an organization node.
*
* @author Christian Hartl, FOCONIS AG
*/
@Entity
@MappedSuperclass
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "kind")
@Index(unique = false, columnNames = "kind")
public abstract class OrganizationNode {
@Id
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@OneToOne(cascade = {})
@NotNull
private OrganizationTreeNode parentTreeNode;
public OrganizationTreeNode getParentTreeNode() {
return parentTreeNode;
}
public void setParentTreeNode(OrganizationTreeNode parentTreeNode) {
this.parentTreeNode = parentTreeNode;
}
}
@@ -0,0 +1,55 @@
package org.tests.inheritance;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.validation.constraints.NotNull;
import io.ebean.annotation.PrivateOwned;
/**
* Model class to reference an organization tree node.
*
* @author Christian Hartl, FOCONIS AG
*/
@Entity
public class OrganizationTreeNode {
@Id
private Long id;
private String name;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "parentTreeNode")
@NotNull
@PrivateOwned
private OrganizationNode organizationNode;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public OrganizationNode getOrganizationNode() {
return organizationNode;
}
public void setOrganizationNode(OrganizationNode organizationNode) {
this.organizationNode = organizationNode;
}
}
@@ -0,0 +1,20 @@
package org.tests.inheritance;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("Unit")
public class OrganizationUnit extends OrganizationNode {
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
@@ -0,0 +1,28 @@
package org.tests.inheritance;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class TestTreeOrganisations extends BaseTestCase {
@Test
public void test() {
OrganizationTreeNode treeNode = new OrganizationTreeNode();
treeNode.setName("tree");
OrganizationUnit node = new OrganizationUnit();
node.setTitle("node");
treeNode.setOrganizationNode(node);
Ebean.save(treeNode);
treeNode = Ebean.find(OrganizationTreeNode.class, treeNode.getId());
assertEquals(node, treeNode.getOrganizationNode());
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
package org.tests.merge;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
@@ -8,7 +9,7 @@ import java.util.UUID;
@MappedSuperclass
public class MBase {
@Id
@Id @GeneratedValue
private UUID id;
@Version
+34
View File
@@ -0,0 +1,34 @@
package org.tests.merge;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class MGroup {
@Id
private long id;
private String name;
public MGroup(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -0,0 +1,59 @@
package org.tests.merge;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Version;
import java.util.ArrayList;
import java.util.List;
@Entity
public class MMachine {
@Id
private long id;
private String name;
@ManyToMany
private List<MGroup> groups = new ArrayList<>();
@Version
private long version;
public MMachine(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public List<MGroup> getGroups() {
return groups;
}
public void setGroups(List<MGroup> groups) {
this.groups = groups;
}
}
@@ -20,6 +20,21 @@ public class TestMergeCustomer extends BaseTestCase {
private Random random = new Random();
@Test
public void customerOnly_defaultOptions_expect_updateOnly() {
MCustomer mCustomer = partial("cust1", "(id,name,version)");
mCustomer.setName("NotCust0");
LoggedSqlCollector.start();
Ebean.merge(mCustomer);
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update mcustomer set name=?, version=? where id=? and version=?");
}
/**
* So this is effectively the same as a stateless update.
*/
@@ -0,0 +1,64 @@
package org.tests.merge;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.MergeOptions;
import io.ebean.MergeOptionsBuilder;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestMergeM2M extends BaseTestCase {
@Test
public void m2mMerge() {
MGroup group1 = new MGroup(1, "gone");
MGroup group2 = new MGroup(2, "gtwo");
MGroup group3 = new MGroup(3, "gthree");
MGroup group4 = new MGroup(4, "gfour");
MGroup group5 = new MGroup(5, "gfive");
Ebean.save(group1);
Ebean.save(group2);
Ebean.save(group3);
Ebean.save(group4);
Ebean.save(group5);
MMachine machine = new MMachine("mac1");
machine.getGroups().add(group1);
machine.getGroups().add(group2);
machine.getGroups().add(group3);
MergeOptions options = new MergeOptionsBuilder().addPath("groups").build();
LoggedSqlCollector.start();
Ebean.merge(machine, options);
List<String> sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(3);
assertThat(sql.get(0)).contains("select");
assertThat(sql.get(1)).contains("insert into mmachine");
assertThat(sql.get(2)).contains("insert into mmachine_mgroup");
machine.setName("mac1-mod");
machine.getGroups().remove(group2);
machine.getGroups().remove(group3);
machine.getGroups().add(group4);
machine.getGroups().add(group5);
Ebean.merge(machine, options);
sql = LoggedSqlCollector.current();
assertThat(sql).hasSize(4);
assertThat(sql.get(0)).contains("select");
assertThat(sql.get(1)).contains("delete from mmachine_mgroup");
assertThat(sql.get(2)).contains("insert into mmachine_mgroup");
assertThat(sql.get(3)).contains("update mmachine");
}
}
@@ -9,6 +9,7 @@ import io.ebean.annotation.Index;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@@ -25,7 +26,7 @@ import java.util.List;
@Cache(naturalKey = "email")
public class Contact {
@Id
@Id @GeneratedValue
int id;
@Size(max=127)
@@ -0,0 +1,87 @@
package org.tests.model.basic;
import io.ebean.annotation.Encrypted;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.time.LocalDate;
@Entity
@Table(name = "e_basicenc_client")
public class EBasicEncryptClient {
public enum Status {
ONE,
TWO
}
@Id
long id;
String name;
@Encrypted(dbLength = 80, dbEncryption = false)
String description;
@Encrypted(dbLength = 20, dbEncryption = false)
LocalDate dob;
@Enumerated(EnumType.ORDINAL)
@Encrypted(dbLength = 20, dbEncryption = false)
Status status;
@Version
long version;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getDob() {
return dob;
}
public void setDob(LocalDate dob) {
this.dob = dob;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -1,13 +1,14 @@
package org.tests.model.bridge;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.UUID;
@Entity
public class BSite {
@Id
@Id @GeneratedValue
UUID id;
String name;
@@ -7,7 +7,9 @@ import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.MapKeyColumn;
import javax.persistence.Transient;
import javax.persistence.Version;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -25,6 +27,9 @@ public class EcmPerson {
@Column(name = "number", length = 10)
Map<String, String> phoneNumbers = new LinkedHashMap<>();
@Transient
Map<String, String> transientPhoneNumbers;
@Version
long version;
@@ -5,6 +5,7 @@ import io.ebean.Ebean;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -137,6 +138,7 @@ public class TestElementCollectionBasicMap extends BaseTestCase {
}
private void jsonToFrom(EcmPerson foundFirst) {
foundFirst.transientPhoneNumbers = new HashMap<>();
String asJson = Ebean.json().toJson(foundFirst);
EcmPerson fromJson = Ebean.json().toBean(EcmPerson.class, asJson);
@@ -8,6 +8,8 @@ import io.ebeaninternal.api.SpiTransaction;
import org.junit.Test;
import org.tests.idkeys.db.AuditLog;
import java.util.Arrays;
import static org.junit.Assert.assertFalse;
public class TestInsertSqlLogging extends BaseTestCase {
@@ -39,6 +41,38 @@ public class TestInsertSqlLogging extends BaseTestCase {
insert.setNextParameter("bob");
insert.execute();
txn.commit();
}
}
@Test
public void addBatch_executeBatch() {
Ebean.find(AuditLog.class).where().ge("id", 10000).delete();
String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
SqlUpdate insert = Ebean.createSqlUpdate(sql);
try (Transaction txn = Ebean.beginTransaction()) {
insert.setNextParameter(10000);
insert.setNextParameter("hello");
insert.setNextParameter("rob");
insert.addBatch();
insert.setNextParameter(10001);
insert.setNextParameter("goodbye");
insert.setNextParameter("rob");
insert.addBatch();
insert.setNextParameter(10002);
insert.setNextParameter("chow");
insert.setNextParameter("bob");
insert.addBatch();
int[] rows = insert.executeBatch();
System.out.println("Rows was " + Arrays.toString(rows));
txn.commit();
}
@@ -0,0 +1 @@
io.ebeantest.CapturingLoggerFactory