mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3b1996dc9 | ||
|
|
24ca744e06 | ||
|
|
7f9ce5bdd4 | ||
|
|
23e5de0819 | ||
|
|
c23508819f | ||
|
|
af030e3da4 | ||
|
|
d8e67d09a9 | ||
|
|
66c08e663e | ||
|
|
9a096e272e | ||
|
|
21e87e12cb | ||
|
|
7c6d7dd4d0 | ||
|
|
c5329e9db0 | ||
|
|
bccb9f8ddb | ||
|
|
c57af29fca | ||
|
|
37b6a321ab | ||
|
|
896f7d9ea8 | ||
|
|
141a0d9358 | ||
|
|
5a7f24da5d | ||
|
|
8ea86d6c6d | ||
|
|
fb5eeb60b1 | ||
|
|
33ec8abb9d | ||
|
|
bbd7baf1be | ||
|
|
2e36402a76 | ||
|
|
fa4af4ef98 | ||
|
|
ed6a9e2dbb | ||
|
|
42d83705b7 | ||
|
|
ae58d9223a | ||
|
|
a9a89938f9 | ||
|
|
4b91f5fe0b | ||
|
|
85d3caf079 | ||
|
|
f6d8c8b618 |
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-10
@@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user