Compare commits

..
Author SHA1 Message Date
rob bygrave e3b1996dc9 [maven-release-plugin] prepare release ebean-11.16.1 2018-05-30 03:36:24 +12:00
rob bygrave 24ca744e06 Bump version to 11.16.1-SNAPSHOT 2018-05-30 03:35:13 +12:00
rob bygrave 7f9ce5bdd4 #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:38 +12:00
rob bygrave 23e5de0819 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement 2018-05-30 03:32:23 +12:00
rob bygrave c23508819f #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:00 +12:00
rob bygrave af030e3da4 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement
ENH: Add merge() API (using default merge options) to EbeanServer, Ebean and BeanRepository enhancement
2018-05-30 03:31:32 +12:00
rob bygrave d8e67d09a9 #1393 - MergeOptionsBuilder build() return MergeOptions interface type 2018-05-29 15:30:39 +12:00
rob bygrave 66c08e663e #1392 - MergeHandler, change regex Pattern to be static 2018-05-29 15:25:38 +12:00
rob bygrave 9a096e272e #1392 - MergeOptionsBuilder setClientGeneratedIds() ... should not be required if the Id property does not have Identity/Sequence generator assigned 2018-05-29 15:24:32 +12:00
rob bygrave 21e87e12cb Adjust test for #1391 ... such that OrganizationTreeNode has at least one db column as part of insert (for PG) 2018-05-29 13:38:17 +12:00
rob bygrave 7c6d7dd4d0 Merge branch 'FOCONIS-fix-create-reference-bean' 2018-05-29 13:28:32 +12:00
Roland Praml c5329e9db0 reformatting 2018-05-28 17:05:52 +02:00
chrishartlandRoland Praml bccb9f8ddb Fix/create reference with descriptor using inheritance (#13)
* FIXED: Discriminator must be ask for inherited classes

* Fixed: Compile

* Refactoring and Testing
2018-05-28 16:54:53 +02:00
rob bygrave c57af29fca [maven-release-plugin] prepare for next development iteration 2018-05-29 00:44:36 +12:00
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
rob bygrave fb5eeb60b1 [maven-release-plugin] prepare release ebean-11.15.11 2018-05-27 19:19:37 +12:00
rob bygrave 33ec8abb9d No effective change - update test remove unused exception 2018-05-27 17:38:33 +12:00
rob bygrave bbd7baf1be #1388 - AutoTune - tuned query includes "null" as fetched property 2018-05-27 17:33:26 +12:00
rob bygrave 2e36402a76 #1387 - Revert #1374 and expose io.ebean.event.ServletContextListener (as public API) 2018-05-27 15:54:32 +12:00
rob bygrave fa4af4ef98 Revert "#1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused"
This reverts commit 95737cd3c5.
2018-05-27 15:41:51 +12:00
rob bygrave ed6a9e2dbb #1386 - ENH: For DbMigration generation - add support for setting version, name, generatePendingDrop, applyPrefix, ddl header 2018-05-27 15:37:45 +12:00
rob bygrave 42d83705b7 #1384 - Change MigrationXmlWriter to not write <!DOCTYPE xml> (as it is not needed/expected now) 2018-05-24 00:15:25 +12:00
rob bygrave ae58d9223a No effective change - add test for PrimaryKeyJoinColumn - insertWithoutExtra 2018-05-24 00:07:30 +12:00
rob bygrave a9a89938f9 #1368 - Remove unused resource - default-create-table.sql ... which is in ebean-migration 2018-05-24 00:06:18 +12:00
rob bygrave 4b91f5fe0b #1380 - Make javax.xml an optional dependency (as it is not part of JDK from 9 onwards) 2018-05-21 01:06:26 +12:00
rob bygrave 85d3caf079 #1367 - Modularized project cannot locate ebean.properties file on Java 10 2018-05-21 00:29:04 +12:00
rob bygrave f6d8c8b618 [maven-release-plugin] prepare for next development iteration 2018-05-21 00:13:59 +12:00
71 changed files with 1431 additions and 139 deletions
+9 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.10</version>
<version>11.16.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.10</tag>
<tag>ebean-11.16.1</tag>
</scm>
<profiles>
@@ -174,6 +174,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
@@ -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.
*/
@@ -402,9 +402,20 @@ public class DbMigrationConfig {
* You can use placeholders like ${version} or ${timestamp} in properties file.
*/
public String getDdlHeader() {
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
return ddlHeader;
}
/**
* Set the header prepended to the DDL.
*/
public void setDdlHeader(String ddlHeader) {
this.ddlHeader = ddlHeader;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
@@ -475,10 +486,6 @@ public class DbMigrationConfig {
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
dbPassword = properties.get("migration.dbpassword", adminPwd);
ddlHeader = properties.get("ddl.header", ddlHeader);
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
}
/**
@@ -493,6 +493,11 @@ public class ServerConfig {
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1967,7 +1972,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2772,6 +2777,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -3109,6 +3115,23 @@ public class ServerConfig {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -37,7 +37,7 @@ class LoadContext {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
is = getClass().getResourceAsStream("/" + resourcePath);
is = resourceStream(resourcePath);
if (is != null) {
loadedResources.add(resourcePath);
}
@@ -56,6 +56,15 @@ class LoadContext {
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
/**
* Add a property entry.
*/
@@ -93,6 +93,38 @@ public interface DbMigration {
*/
void setStrictMode(boolean strictMode);
/**
* Set to true to include a generated header comment in the DDL script.
*/
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
/**
* Set the header that is included in the generated DDL script.
*/
void setHeader(String header);
/**
* Set the prefix for the version. Set this to "V" for use with Flyway.
*/
void setApplyPrefix(String applyPrefix);
/**
* Set the version of the migration to be generated.
*/
void setVersion(String version);
/**
* Set the name of the migration to be generated.
*/
void setName(String name);
/**
* Generate a migration for the version specified that contains pending drops.
*
* @param generatePendingDrop The version of a prior migration that holds pending drops.
*/
void setGeneratePendingDrop(String generatePendingDrop);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -0,0 +1,38 @@
package io.ebean.event;
import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Listens for webserver server starting and stopping events.
* <p>
* This should be used when the deployment is into a servlet container where the webapp
* can be shutdown or redeployed without the JVM stopping.
* </p>
* <p>
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
* runnable war or when using a servlet container that only contains the single webapp and
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
* hook that Ebean registers.
* </p>
*/
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
* The servlet container is stopping.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
ShutdownManager.shutdown();
}
/**
* Do nothing on startup.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
}
}
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
}
Props getProps(String path) {
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
return props;
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
}
public Collection<Props> getPathProps() {
@@ -268,4 +268,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);
}
@@ -91,6 +91,12 @@ public class DefaultDbMigration implements DbMigration {
protected DbConstraintNaming constraintNaming;
protected Boolean strictMode;
protected Boolean includeGeneratedFileComment;
protected String header;
protected String applyPrefix;
protected String version;
protected String name;
protected String generatePendingDrop;
/**
* Create for offline migration generation.
@@ -148,6 +154,36 @@ public class DefaultDbMigration implements DbMigration {
this.strictMode = strictMode;
}
@Override
public void setApplyPrefix(String applyPrefix) {
this.applyPrefix = applyPrefix;
}
@Override
public void setVersion(String version) {
this.version = version;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
@Override
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
@Override
public void setHeader(String header) {
this.header = header;
}
/**
* Set the specific platform to generate DDL for.
* <p>
@@ -512,8 +548,28 @@ public class DefaultDbMigration implements DbMigration {
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
if (strictMode != null && migrationConfig != null) {
migrationConfig.setStrictMode(strictMode);
if (migrationConfig != null) {
if (strictMode != null) {
migrationConfig.setStrictMode(strictMode);
}
if (applyPrefix != null) {
migrationConfig.setApplyPrefix(applyPrefix);
}
if (header != null) {
migrationConfig.setDdlHeader(header);
}
if (includeGeneratedFileComment != null) {
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
}
if (version != null) {
migrationConfig.setVersion(version);
}
if (name != null) {
migrationConfig.setName(name);
}
if (generatePendingDrop != null) {
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
}
}
}
@@ -29,7 +29,6 @@ public class MigrationXmlWriter {
try (FileWriter writer = new FileWriter(file)) {
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
writer.write("<!DOCTYPE xml>\n");
if (comment != null) {
writer.write("<!-- ");
writer.write(comment);
@@ -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;
@@ -65,6 +66,7 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
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;
@@ -919,6 +921,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);
@@ -1961,6 +1968,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return persister.executeSqlUpdate(updSql, t);
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
persister.addBatch(sqlUpdate, transaction);
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
return persister.executeBatch(sqlUpdate, transaction);
}
/**
* Execute the updateSql.
*/
@@ -1,11 +1,12 @@
package io.ebeaninternal.server.core;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebean.Update;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import java.io.Serializable;
@@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private static final long serialVersionUID = -6493829438421253102L;
private transient final EbeanServer server;
private transient final SpiEbeanServer server;
/**
* The parameters used to bind to the sql.
@@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private Object generatedKey;
/**
* Set when batching explicitly used.
*/
private boolean batched;
/**
* Transaction used for addBatch() executeBatch() processing.
*/
private transient SpiTransaction transaction;
/**
* Create with server sql and bindParams object.
* <p>
@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* same time.
* </p>
*/
public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) {
this.server = server;
this.sql = sql;
this.bindParams = bindParams;
@@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* Create with a specific server. This means you can use the
* SqlUpdate.execute() method.
*/
public DefaultSqlUpdate(EbeanServer server, String sql) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql) {
this(server, sql, new BindParams());
}
@@ -111,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
@Override
public int execute() {
if (server != null) {
if (batched) {
server.executeBatch(this, transaction);
return -1;
}
return server.execute(this);
} else {
// Hopefully this doesn't catch anyone out...
@@ -118,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public int[] executeBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (!batched) {
throw new IllegalStateException("No prior addBatch() called?");
}
return server.executeBatch(this, transaction);
}
@Override
public void addBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (transaction == null) {
transaction = server.currentServerTransaction();
if (transaction == null) {
throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()");
}
}
batched = true;
server.addBatch(this, transaction);
}
@Override
public Object getGeneratedKey() {
return generatedKey;
@@ -34,8 +34,9 @@ class InternalConfigXmlRead {
InternalConfigXmlRead(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
this.classLoader = serverConfig.getClassLoadConfig().getClassLoader();
init();
if (serverConfig.getClassLoadConfig().isJavaxJAXBPresent()) {
init();
}
}
private void init() {
@@ -73,10 +74,12 @@ class InternalConfigXmlRead {
* Return the named queries for Dto beans.
*/
Map<Class<?>, DtoNamedQueries> readDtoMapping() {
for (XmEbean mapping : xmlEbeanList) {
List<XmDto> dtoList = mapping.getDto();
for (XmDto dto : dtoList) {
readDtoMapping(dto);
if (xmlEbeanList != null) {
for (XmEbean mapping : xmlEbeanList) {
List<XmDto> dtoList = mapping.getDto();
for (XmDto dto : dtoList) {
readDtoMapping(dto);
}
}
}
@@ -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);
}
@@ -0,0 +1,35 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Deprecated - migrate to io.ebean.event.ServletContextListener.
* <p>
* Listens for webserver server starting and stopping events.
* <p>
* Register this listener in the web.xml configuration file. This will listen
* for startup and shutdown events.
* </p>
*/
@Deprecated
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
* The servlet container is stopping.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
ShutdownManager.shutdown();
}
/**
* Do nothing on startup.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
}
}
@@ -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).
*/
@@ -394,12 +394,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
private void readXmlMapping(List<XmEbean> mappings) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
if (mappings != null) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
}
}
}
}
@@ -1368,9 +1369,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setIdType(IdType.EXTERNAL);
return;
}
// use the default. IDENTITY or SEQUENCE.
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
if (desc.isIdGeneratedValue() || serverConfig.isIdGeneratorAutomatic()) {
// use IDENTITY or SEQUENCE based on platform
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
} else {
// externally/application supplied Id values
desc.setIdType(IdType.EXTERNAL);
return;
}
}
if (desc.getBaseTable() == null) {
@@ -1386,21 +1393,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return;
}
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (IdType.SEQUENCE == desc.getIdType()) {
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
@@ -7,10 +7,10 @@ import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -50,6 +50,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
private final String intersectionPublishTable;
private final String intersectionDraftTable;
private IntersectionTable intersectionTable;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
@@ -157,7 +159,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
if (elementCollection) {
targetDescriptor = elementDescriptor;
} else {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
super.initialiseTargetDescriptor(initContext);
}
}
@@ -352,10 +354,30 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
*/
public void setLoader(BeanCollectionLoader loader) {
public void setEbeanServer(SpiEbeanServer server) {
if (help != null) {
help.setLoader(loader);
help.setLoader(server);
}
if (manyToMany) {
intersectionTable = initIntersectionTable();
}
}
private IntersectionTable initIntersectionTable() {
IntersectionBuilder row = new IntersectionBuilder(intersectionPublishTable, intersectionDraftTable);
for (ExportedProperty exportedProperty : exportedProperties) {
row.addColumn(exportedProperty.getForeignDbColumn());
}
importedId.buildImport(row);
return row.build();
}
/**
* Return the intersection table helper.
*/
public IntersectionTable intersectionTable() {
return intersectionTable;
}
/**
@@ -755,6 +777,20 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable);
}
/**
* Bind the two side of a many to many to the given SqlUpdate.
*/
public void intersectionBind(SqlUpdate sql, EntityBean parentBean, EntityBean other) {
if (embeddedExportedProperties) {
BeanProperty idProp = descriptor.getIdProperty();
parentBean = (EntityBean) idProp.getValue(parentBean);
}
for (ExportedProperty exportedProperty : exportedProperties) {
sql.setNextParameter(exportedProperty.getValue(parentBean));
}
importedId.bindImport(sql, other);
}
private void buildExport(IntersectionRow row, EntityBean parentBean) {
if (embeddedExportedProperties) {
@@ -46,15 +46,16 @@ class BeanPropertyAssocManyJsonHelp {
if (JsonToken.VALUE_NULL == event) {
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array ");
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
}
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
@@ -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.
*/
@@ -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);
@@ -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.
*
@@ -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).
*/
@@ -143,6 +143,8 @@ public final class DefaultTypeManager implements TypeManager {
private final boolean java7Present;
private final boolean objectMapperPresent;
private final boolean postgres;
private final boolean offlineMigrationGeneration;
@@ -184,7 +186,7 @@ public final class DefaultTypeManager implements TypeManager {
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
boolean objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.extraTypeFactory = new DefaultTypeFactory(config);
@@ -405,20 +407,22 @@ public final class DefaultTypeManager implements TypeManager {
}
}
if (type.equals(JsonNode.class)) {
switch (dbType) {
case Types.VARCHAR:
return jsonNodeVarchar;
case Types.BLOB:
return jsonNodeBlob;
case Types.CLOB:
return jsonNodeClob;
case DbPlatformType.JSONB:
return jsonNodeJsonb;
case DbPlatformType.JSON:
return jsonNodeJson;
default:
return jsonNodeJson;
if (objectMapperPresent) {
if (type.equals(JsonNode.class)) {
switch (dbType) {
case Types.VARCHAR:
return jsonNodeVarchar;
case Types.BLOB:
return jsonNodeBlob;
case Types.CLOB:
return jsonNodeClob;
case DbPlatformType.JSONB:
return jsonNodeJsonb;
case DbPlatformType.JSON:
return jsonNodeJson;
default:
return jsonNodeJson;
}
}
}
@@ -1,13 +0,0 @@
create table ${table} (
id integer not null,
mtype varchar(1) not null,
mstatus varchar(10) not null,
mversion varchar(150) not null,
mcomment varchar(150) not null,
mchecksum integer not null,
run_on timestamp not null,
run_by varchar(30) not null,
run_time integer not null,
constraint pk_${table} primary key (id)
);
@@ -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());
}
}
@@ -237,6 +237,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return 0;
}
@Override
public void merge(Object bean) {
}
@Override
public void merge(Object bean, MergeOptions options) {
@@ -800,6 +805,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,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)
@@ -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);
@@ -21,6 +21,25 @@ public class TestOneToOnePrimaryKeyJoinOptional extends BaseTestCase {
return prime;
}
@Test
public void insertWithoutExtra() {
String desc = "" + System.currentTimeMillis();
OtoUPrime p1 = new OtoUPrime("u" + desc);
Ebean.save(p1);
Query<OtoUPrime> query = Ebean.find(OtoUPrime.class)
.setId(p1.getPid())
.fetch("extra", "eid");
OtoUPrime found = query.findOne();
if (found.getExtra() != null) {
found.getExtra().getExtra(); // fails here, because getExtra should be null
}
assertThat(found.getExtra()).isNull();
}
@Test
public void insertUpdateDelete() {
@@ -17,7 +17,7 @@ public class TestAutoTuneProfiling extends BaseTestCase {
@Ignore
@Test
public void test() throws InterruptedException {
public void test() {
ResetBasicData.reset();
@@ -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();
}
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xml>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply">
<createTable name="migtest_ckey_assoc" pkName="pk_migtest_ckey_assoc">
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xml>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply">
<addColumn tableName="migtest_ckey_detail">
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xml>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply" dropsFor="1.1">
<dropColumn columnName="old_boolean" tableName="migtest_e_basic"/>
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xml>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply">
<alterForeignKey name="fk_migtest_ckey_detail_parent" columnNames="DROP FOREIGN KEY" indexName="ix_migtest_ckey_detail_parent" tableName="migtest_ckey_detail"/>
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE xml>
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
<changeSet type="apply" dropsFor="1.3">
<dropColumn columnName="one_key" tableName="migtest_ckey_detail"/>
+4 -3
View File
@@ -11,9 +11,10 @@
ebean.encryptKeyManager=org.tests.basic.encrypt.BasicEncyptKeyManager
#ebean.autotune.querytuning=true
#ebean.autotune.profiling=true
#ebean.autotune.profilingUpdateFrequency=5
#ebean.disableL2Cache=true
#ebean.autoTune.queryTuning=true
#ebean.autoTune.profiling=true
#ebean.autoTune.profilingUpdateFrequency=5
ebean.ddl.generate=true
ebean.ddl.run=true