mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa004f11af | ||
|
|
acd5578d8a | ||
|
|
07946cd505 | ||
|
|
57870aaa05 | ||
|
|
cca837bcfa | ||
|
|
534eb2f0e2 | ||
|
|
dfa7b765f9 | ||
|
|
4ef581f87c | ||
|
|
eb96c726fd | ||
|
|
abbcd35ee6 | ||
|
|
db5a2b194d | ||
|
|
74e0a16531 | ||
|
|
8601f51a6c | ||
|
|
6534feb5da | ||
|
|
6cf5e3386d | ||
|
|
7651256083 | ||
|
|
057d7480b0 | ||
|
|
85c4385498 | ||
|
|
74a9019535 | ||
|
|
c44931ea25 | ||
|
|
012322503f | ||
|
|
5575273c96 | ||
|
|
f3251691e2 | ||
|
|
9d336cb20b | ||
|
|
9895e48485 | ||
|
|
4799c56e25 | ||
|
|
602a35d0b6 | ||
|
|
4bc1f2ede4 | ||
|
|
674617eb9e | ||
|
|
afead2da41 | ||
|
|
607cf3156c | ||
|
|
af2b8976fa | ||
|
|
85bfe3d03e | ||
|
|
eda6350d89 | ||
|
|
8ac1b745df | ||
|
|
6c8cf0c5b7 | ||
|
|
d0b24dc340 | ||
|
|
2042bdf29a | ||
|
|
4f327c2751 | ||
|
|
fbc2ba3912 | ||
|
|
93d659050b | ||
|
|
2520837f5e | ||
|
|
2f349ff054 | ||
|
|
0aec597cf2 | ||
|
|
5faec61cd2 | ||
|
|
7252e2d7d9 | ||
|
|
0cc87009d0 | ||
|
|
ef58954af5 | ||
|
|
824f5e468e | ||
|
|
d84cdab714 | ||
|
|
5a3550e603 | ||
|
|
f840cfc2ae | ||
|
|
8e0d2f8749 | ||
|
|
eef514f922 | ||
|
|
4fc7898f85 | ||
|
|
6258a5d263 | ||
|
|
dbf5f3b57b | ||
|
|
20e46cad9b | ||
|
|
9953ef35eb |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.14.3</version>
|
||||
<version>11.15.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.14.3</tag>
|
||||
<tag>ebean-11.15.2</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -112,13 +112,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.8</version>
|
||||
<version>3.11</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -130,7 +130,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.3.1</version>
|
||||
<version>11.5.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -224,7 +224,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.10.1</version>
|
||||
<version>11.11.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -254,7 +254,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.1.3.jre8-preview</version>
|
||||
<version>6.4.0.jre8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.10.1</version>
|
||||
<version>11.11.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -638,6 +638,16 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
public static void merge(Object bean, MergeOptions options) {
|
||||
serverMgr.getDefaultServer().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans from a Collection.
|
||||
*/
|
||||
|
||||
@@ -437,6 +437,19 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
|
||||
|
||||
/**
|
||||
* Create a named Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param namedQuery The name of the query
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
|
||||
|
||||
/**
|
||||
* Create a SqlQuery for executing native sql
|
||||
* query statements.
|
||||
@@ -508,30 +521,44 @@ public interface EbeanServer {
|
||||
* etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>Using try with resources</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* ebeanServer.beginTransaction();
|
||||
*
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
*
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
* ...
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Using try finally block</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
*
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* ebeanServer.commitTransaction();
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if we didn't commit
|
||||
* // i.e. an exception occurred before commitTransaction().
|
||||
* ebeanServer.endTransaction();
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Transaction options:</h3>
|
||||
* <h3>Transaction options</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
* // explicitly turn on/off JDBC batch use
|
||||
* txn.setBatchMode(true);
|
||||
* txn.setBatchSize(50);
|
||||
@@ -552,10 +579,6 @@ public interface EbeanServer {
|
||||
* ...
|
||||
*
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if necessary
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -585,19 +608,16 @@ public interface EbeanServer {
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
* // suspend it until this transaction ends
|
||||
* Transaction txn = server.beginTransaction(TxScope.requiresNew());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.requiresNew())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which:
|
||||
* // A) will rollback transaction if it has not been committed already
|
||||
* // At end this transaction will:
|
||||
* // A) will rollback transaction if it has not been committed
|
||||
* // B) will restore a previously suspended transaction
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -606,20 +626,13 @@ public interface EbeanServer {
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a new transaction if there is not a current transaction
|
||||
* Transaction txn = server.beginTransaction(TxScope.required());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.required())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction if it was created or
|
||||
* // do nothing if there was already a current transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which will rollback the transaction
|
||||
* // if it was created for this try finally scope and has not
|
||||
* // already been committed
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -1615,6 +1628,22 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options and a transaction.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Insert the bean.
|
||||
* <p>
|
||||
|
||||
@@ -327,6 +327,30 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression allEq(Map<String, Object> propertyMap);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -482,7 +482,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
@@ -545,6 +545,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> setDisableReadAuditing();
|
||||
|
||||
/**
|
||||
* Set a label on the query (to help identify query execution statistics).
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Add expressions to the having clause.
|
||||
* <p>
|
||||
@@ -873,7 +878,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -883,7 +888,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -893,7 +898,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - property has a value in the collection of values.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -991,6 +996,53 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> arrayIsNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAny("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Flags Bulk + Size = Size
|
||||
* // ... meaning Bulk is not set and Size is set
|
||||
*
|
||||
* long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
|
||||
* long mask = BwFlags.HAS_SIZE; // Only Size flag set
|
||||
*
|
||||
* where().bitwiseAnd("flags", selectedFlags, mask)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
|
||||
* <p>
|
||||
* Instances of MergeOptions are thread safe and safe to share across threads.
|
||||
*/
|
||||
public interface MergeOptions {
|
||||
|
||||
/**
|
||||
* Returns true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
boolean isClientGeneratedIds();
|
||||
|
||||
/**
|
||||
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
|
||||
*/
|
||||
boolean isDeletePermanent();
|
||||
|
||||
/**
|
||||
* Return the paths included in the merge.
|
||||
*/
|
||||
Set<String> paths();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Builds a MergeOptions which is immutable and thread safe.
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent = true;
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
*
|
||||
* @param path The path relative to the root type.
|
||||
* @return The builder to chain another addPath() or build().
|
||||
*/
|
||||
public MergeOptionsBuilder addPath(String path) {
|
||||
paths.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
public MergeOptionsBuilder setClientGeneratedIds() {
|
||||
this.clientGeneratedIds = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
|
||||
*/
|
||||
public MergeOptionsBuilder setDeletePermanent() {
|
||||
this.deletePermanent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
private static class MOptions implements MergeOptions {
|
||||
|
||||
private final boolean clientGeneratedIds;
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientGeneratedIds() {
|
||||
return clientGeneratedIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeletePermanent() {
|
||||
return deletePermanent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1419,6 +1419,15 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set a label on the query.
|
||||
* <p>
|
||||
* This label can be used to help identify query performance metrics but we can also use
|
||||
* profile location enhancement on Finders so for some that would be a better option.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
|
||||
@@ -115,6 +115,11 @@ public interface SqlQuery extends Serializable {
|
||||
*/
|
||||
SqlQuery setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set a label that can be put on performance metrics that are collected.
|
||||
*/
|
||||
SqlQuery setLabel(String label);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
|
||||
@@ -30,6 +30,35 @@ package io.ebean;
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Example: Using setNextParameter()</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
|
||||
* SqlUpdate insert = Ebean.createSqlUpdate(sql);
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
* txn.setBatchMode(true);
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.execute();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Update
|
||||
* @see SqlQuery
|
||||
@@ -61,7 +90,7 @@ public interface SqlUpdate {
|
||||
|
||||
/**
|
||||
* Execute and return the generated key. This is effectively a short cut for:
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* sqlUpdate.execute();
|
||||
@@ -137,6 +166,13 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set the next positioned parameter.
|
||||
*
|
||||
* @param value The value to bind
|
||||
*/
|
||||
SqlUpdate setNextParameter(Object value);
|
||||
|
||||
/**
|
||||
* Set a parameter via its index position.
|
||||
*/
|
||||
|
||||
@@ -152,6 +152,11 @@ public interface Update<T> {
|
||||
*/
|
||||
Update<T> setNullParameter(String name, int jdbcType);
|
||||
|
||||
/**
|
||||
* Set a label meaning performance metrics will be collected for the execution of this update.
|
||||
*/
|
||||
Update<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return the sql that is actually executed.
|
||||
*/
|
||||
|
||||
@@ -72,6 +72,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
boolean isSkipSave();
|
||||
|
||||
/**
|
||||
* Return true if the collection holds modifications.
|
||||
*/
|
||||
boolean holdsModifications();
|
||||
|
||||
/**
|
||||
* Return the bean that owns this collection.
|
||||
*/
|
||||
|
||||
@@ -52,6 +52,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private int state;
|
||||
|
||||
private boolean forceUpdate;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
private boolean dirty;
|
||||
@@ -131,6 +133,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.nodeUsageCollector = usageCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ownerId (IdClass).
|
||||
*/
|
||||
public Object getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ownerId (IdClass).
|
||||
*/
|
||||
public void setOwnerId(Object ownerId) {
|
||||
this.ownerId = ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the owning bean for an embedded bean.
|
||||
*/
|
||||
@@ -292,6 +308,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean to be updated when persisted (for merge).
|
||||
*/
|
||||
public void setForceUpdate(boolean forceUpdate) {
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity should be updated.
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return forceUpdate || state == STATE_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity has been loaded.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -64,7 +63,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.ebeanServerName = loader.getName();
|
||||
this.ownerBean = ownerBean;
|
||||
this.propertyName = propertyName;
|
||||
this.readOnly = ownerBean._ebean_getIntercept().isReadOnly();
|
||||
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,13 +95,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
if (loader == null) {
|
||||
loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName);
|
||||
}
|
||||
if (loader == null) {
|
||||
String msg = "Lazy loading but LazyLoadEbeanServer is null?"
|
||||
+ " The LazyLoadEbeanServer needs to be set after deserialization"
|
||||
+ " to support lazy loading.";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
loader.loadMany(this, onlyIds);
|
||||
checkEmptyLazyLoad();
|
||||
}
|
||||
@@ -215,7 +207,8 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
/**
|
||||
* Return true if there are underlying additions or removals.
|
||||
*/
|
||||
boolean holdsModifications() {
|
||||
@Override
|
||||
public boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@ package io.ebean.config;
|
||||
public interface AutoConfigure {
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation.
|
||||
* <p>
|
||||
* Return true if the autoConfiguration applies to this ServerConfig.
|
||||
* Perform configuration for the ServerConfig prior to properties load.
|
||||
*/
|
||||
void configure(ServerConfig serverConfig);
|
||||
void preConfigure(ServerConfig serverConfig);
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation but after properties have been applied.
|
||||
*/
|
||||
void postConfigure(ServerConfig serverConfig);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class DbTypeConfig {
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private ServerConfig.DbUuid dbUuid = ServerConfig.DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public ServerConfig.DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(ServerConfig.DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class PlatformConfig {
|
||||
|
||||
private boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* The database boolean true value (typically either 1, T, or Y).
|
||||
*/
|
||||
private String databaseBooleanTrue;
|
||||
|
||||
/**
|
||||
* The database boolean false value (typically either 0, F or N).
|
||||
*/
|
||||
private String databaseBooleanFalse;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private DbUuid dbUuid = DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct with defaults.
|
||||
*/
|
||||
public PlatformConfig() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct based on given config - typically for DbMigration generation with many platforms.
|
||||
*/
|
||||
public PlatformConfig(PlatformConfig platformConfig) {
|
||||
this.databaseBooleanFalse = platformConfig.databaseBooleanFalse;
|
||||
this.databaseBooleanTrue = platformConfig.databaseBooleanTrue;
|
||||
this.databaseSequenceBatchSize = platformConfig.databaseSequenceBatchSize;
|
||||
this.idType = platformConfig.idType;
|
||||
this.geometrySRID = platformConfig.geometrySRID;
|
||||
this.dbUuid = platformConfig.dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value returned is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanTrue() {
|
||||
return databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value set is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanTrue(String databaseBooleanTrue) {
|
||||
this.databaseBooleanTrue = databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent FALSE in the database.
|
||||
*/
|
||||
public String getDatabaseBooleanFalse() {
|
||||
return databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value used to represent FALSE in the database.
|
||||
*/
|
||||
public void setDatabaseBooleanFalse(String databaseBooleanFalse) {
|
||||
this.databaseBooleanFalse = databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public int getDatabaseSequenceBatchSize() {
|
||||
return databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
|
||||
public void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
setDbUuid(dbUuid);
|
||||
}
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
int srid = p.getInt("geometrySRID", 0);
|
||||
if (srid > 0) {
|
||||
setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
// Mapping is specified in the form: BOOLEAN=int(1);BIT=int(1);
|
||||
String mapping = p.get("mapping");
|
||||
if (mapping != null && !mapping.isEmpty()) {
|
||||
Map<String, String> map = StringHelper.delimitedToMap(mapping, ";", "=");
|
||||
for (Entry<String, String> entry : map.entrySet()) {
|
||||
addCustomMapping(DbType.valueOf(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
if (quotedIdentifiers != allQuotedIdentifiers) {
|
||||
// potentially also set to use matching naming convention
|
||||
setAllQuotedIdentifiers(quotedIdentifiers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if native UUID type is preferred.
|
||||
*/
|
||||
public boolean useNativeType() {
|
||||
return nativeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
|
||||
*/
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,23 @@ public class PropertiesWrapper {
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
private final ClassLoadConfig classLoadConfig;
|
||||
|
||||
/**
|
||||
* Construct with a prefix, serverName and properties.
|
||||
*/
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.properties = properties;
|
||||
this.classLoadConfig = classLoadConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct without prefix of serverName.
|
||||
*/
|
||||
public PropertiesWrapper(Properties properties) {
|
||||
this(null, null, properties);
|
||||
public PropertiesWrapper(Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this(null, null, properties, classLoadConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,4 +118,30 @@ public class PropertiesWrapper {
|
||||
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
public <T> T createInstance(Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,16 +183,6 @@ public class ServerConfig {
|
||||
*/
|
||||
private DatabasePlatform databasePlatform;
|
||||
|
||||
/**
|
||||
* The preferred IdType (to override the default Platform type).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* JDBC fetchSize hint when using findList. Defaults to 0 leaving it up to the JDBC driver.
|
||||
*/
|
||||
@@ -330,18 +320,6 @@ public class ServerConfig {
|
||||
*/
|
||||
private String dataSourceJndiName;
|
||||
|
||||
/**
|
||||
* The database boolean true value (typically either 1, T, or Y).
|
||||
*/
|
||||
private String databaseBooleanTrue;
|
||||
|
||||
/**
|
||||
* The database boolean false value (typically either 0, F or N).
|
||||
*/
|
||||
private String databaseBooleanFalse;
|
||||
|
||||
private boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* The naming convention.
|
||||
*/
|
||||
@@ -368,9 +346,9 @@ public class ServerConfig {
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Database type configuration.
|
||||
* Database platform configuration.
|
||||
*/
|
||||
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
|
||||
private PlatformConfig platformConfig = new PlatformConfig();
|
||||
|
||||
/**
|
||||
* The UUID version to use.
|
||||
@@ -510,6 +488,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean defaultOrderById = false;
|
||||
|
||||
/**
|
||||
* The mappingLocations for searching xml mapping.
|
||||
*/
|
||||
private List<String> mappingLocations = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -946,7 +929,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1128,14 +1111,14 @@ public class ServerConfig {
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return dbTypeConfig.getGeometrySRID();
|
||||
return platformConfig.getGeometrySRID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
dbTypeConfig.setGeometrySRID(geometrySRID);
|
||||
platformConfig.setGeometrySRID(geometrySRID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1425,15 +1408,21 @@ public class ServerConfig {
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return allQuotedIdentifiers;
|
||||
return platformConfig.isAllQuotedIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
if (allQuotedIdentifiers && namingConvention instanceof UnderscoreNamingConvention) {
|
||||
platformConfig.setAllQuotedIdentifiers(allQuotedIdentifiers);
|
||||
if (allQuotedIdentifiers) {
|
||||
adjustNamingConventionForAllQuoted();
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustNamingConventionForAllQuoted() {
|
||||
if (namingConvention instanceof UnderscoreNamingConvention) {
|
||||
// we need to use matching naming convention
|
||||
this.namingConvention = new MatchingNamingConvention();
|
||||
}
|
||||
@@ -1648,7 +1637,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanTrue() {
|
||||
return databaseBooleanTrue;
|
||||
return platformConfig.getDatabaseBooleanTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1661,7 +1650,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanTrue(String databaseTrue) {
|
||||
this.databaseBooleanTrue = databaseTrue;
|
||||
platformConfig.setDatabaseBooleanTrue(databaseTrue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1674,7 +1663,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanFalse() {
|
||||
return databaseBooleanFalse;
|
||||
return platformConfig.getDatabaseBooleanFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1687,14 +1676,14 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanFalse(String databaseFalse) {
|
||||
this.databaseBooleanFalse = databaseFalse;
|
||||
this.platformConfig.setDatabaseBooleanFalse(databaseFalse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public int getDatabaseSequenceBatchSize() {
|
||||
return databaseSequenceBatchSize;
|
||||
return platformConfig.getDatabaseSequenceBatchSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1713,7 +1702,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseSequenceBatch(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
this.platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,7 +1729,7 @@ public class ServerConfig {
|
||||
* that you don't have access to.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values are oracle, h2, postgres, mysql, mssqlserver2005.
|
||||
* Values are oracle, h2, postgres, mysql, sqlserver16, sqlserver17.
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabasePlatformName(String databasePlatformName) {
|
||||
@@ -1769,14 +1758,14 @@ public class ServerConfig {
|
||||
* Return the preferred DB platform IdType.
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
return platformConfig.getIdType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preferred DB platform IdType.
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
this.platformConfig.setIdType(idType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1889,15 +1878,22 @@ public class ServerConfig {
|
||||
/**
|
||||
* Return the configuration for DB types (such as UUID and custom mappings).
|
||||
*/
|
||||
public DbTypeConfig getDbTypeConfig() {
|
||||
return dbTypeConfig;
|
||||
public PlatformConfig getPlatformConfig() {
|
||||
return platformConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for DB platform (such as UUID and custom mappings).
|
||||
*/
|
||||
public void setPlatformConfig(PlatformConfig platformConfig) {
|
||||
this.platformConfig = platformConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbTypeConfig.setDbUuid(dbUuid);
|
||||
public void setDbUuid(PlatformConfig.DbUuid dbUuid) {
|
||||
this.platformConfig.setDbUuid(dbUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1965,6 +1961,16 @@ public class ServerConfig {
|
||||
this.durationWithNanos = durationWithNanos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run DB migrations on server start.
|
||||
* <p>
|
||||
* 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){
|
||||
migrationConfig.setRunMigration(runMigration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to generate the "create all" DDL on startup.
|
||||
* <p>
|
||||
@@ -2353,7 +2359,7 @@ public class ServerConfig {
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
dbTypeConfig.addCustomMapping(type, columnDefinition, platform);
|
||||
platformConfig.addCustomMapping(type, columnDefinition, platform);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2373,7 +2379,7 @@ public class ServerConfig {
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
dbTypeConfig.addCustomMapping(type, columnDefinition);
|
||||
platformConfig.addCustomMapping(type, columnDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2659,19 +2665,24 @@ public class ServerConfig {
|
||||
* Load the settings from the given properties
|
||||
*/
|
||||
private void configureFromProperties() {
|
||||
autoConfiguration();
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
List<AutoConfigure> autoConfigures = autoConfiguration();
|
||||
loadSettings(new PropertiesWrapper("ebean", name, properties, classLoadConfig));
|
||||
for (AutoConfigure autoConfigure : autoConfigures) {
|
||||
autoConfigure.postConfigure(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a 'plugin' to provide automatic configuration. Intended for automatic testing
|
||||
* configuration with Docker containers via ebean-test-config.
|
||||
*/
|
||||
private void autoConfiguration() {
|
||||
private List<AutoConfigure> autoConfiguration() {
|
||||
List<AutoConfigure> list = new ArrayList<>();
|
||||
for (AutoConfigure autoConfigure : serviceLoad(AutoConfigure.class)) {
|
||||
autoConfigure.configure(this);
|
||||
autoConfigure.preConfigure(this);
|
||||
list.add(autoConfigure);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2695,34 +2706,6 @@ public class ServerConfig {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param properties the properties
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = properties.get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the data source settings to preserve existing behaviour. IMHO, if someone has set the datasource config already,
|
||||
* they don't want the settings to be reloaded and reset. This allows a descending class to override this behaviour and prevent it
|
||||
@@ -2756,11 +2739,9 @@ public class ServerConfig {
|
||||
|
||||
profilingConfig.loadSettings(p, name);
|
||||
migrationConfig.loadSettings(p, name);
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
if (quotedIdentifiers != allQuotedIdentifiers) {
|
||||
// potentially also set to use matching naming convention
|
||||
setAllQuotedIdentifiers(quotedIdentifiers);
|
||||
platformConfig.loadSettings(p);
|
||||
if (platformConfig.isAllQuotedIdentifiers()) {
|
||||
adjustNamingConventionForAllQuoted();
|
||||
}
|
||||
namingConvention = createNamingConvention(p, namingConvention);
|
||||
if (namingConvention != null) {
|
||||
@@ -2781,11 +2762,6 @@ public class ServerConfig {
|
||||
}
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
int srid = p.getInt("geometrySRID", 0);
|
||||
if (srid > 0) {
|
||||
dbTypeConfig.setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
@@ -2800,19 +2776,17 @@ public class ServerConfig {
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
currentUserProvider = p.createInstance(CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = p.createInstance(DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = p.createInstance(EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = p.createInstance(EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = p.createInstance(Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = p.createInstance(DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
dbOffline = p.getBoolean("dbOffline", dbOffline);
|
||||
serverCachePlugin = createInstance(p, ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
|
||||
serverCachePlugin = p.createInstance(ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
|
||||
|
||||
if (packages != null) {
|
||||
String packagesProp = p.get("search.packages", p.get("packages", null));
|
||||
packages = getSearchJarsPackages(packagesProp);
|
||||
}
|
||||
String packagesProp = p.get("search.packages", p.get("packages", null));
|
||||
packages = getSearchList(packagesProp, packages);
|
||||
|
||||
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", collectQueryStatsByNode);
|
||||
collectQueryOrigins = p.getBoolean("collectQueryOrigins", collectQueryOrigins);
|
||||
@@ -2828,7 +2802,6 @@ public class ServerConfig {
|
||||
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
|
||||
}
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
|
||||
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
|
||||
|
||||
@@ -2849,20 +2822,9 @@ public class ServerConfig {
|
||||
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
|
||||
jdbcFetchSizeFindEach = p.getInt("jdbcFetchSizeFindEach", jdbcFetchSizeFindEach);
|
||||
jdbcFetchSizeFindList = p.getInt("jdbcFetchSizeFindList", jdbcFetchSizeFindList);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
dbTypeConfig.setDbUuid(dbUuid);
|
||||
}
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
dbTypeConfig.setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
|
||||
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
|
||||
|
||||
@@ -2898,16 +2860,18 @@ public class ServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
|
||||
tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
|
||||
tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
|
||||
currentTenantProvider = p.createInstance(CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
|
||||
tenantCatalogProvider = p.createInstance(TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
|
||||
tenantSchemaProvider = p.createInstance(TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
|
||||
tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
|
||||
classes = getClasses(p);
|
||||
|
||||
String mappingsProp = p.get("mappingLocations", null);
|
||||
mappingLocations = getSearchList(mappingsProp, mappingLocations);
|
||||
}
|
||||
|
||||
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
|
||||
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
|
||||
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingconvention", null);
|
||||
return (nc != null) ? nc : namingConvention;
|
||||
}
|
||||
|
||||
@@ -2940,17 +2904,17 @@ public class ServerConfig {
|
||||
return classes;
|
||||
}
|
||||
|
||||
private List<String> getSearchJarsPackages(String searchPackages) {
|
||||
private List<String> getSearchList(String searchNames, List<String> defaultValue) {
|
||||
|
||||
if (searchPackages != null) {
|
||||
String[] entries = StringHelper.splitNames(searchPackages);
|
||||
if (searchNames != null) {
|
||||
String[] entries = StringHelper.splitNames(searchNames);
|
||||
|
||||
List<String> hitList = new ArrayList<>(entries.length);
|
||||
Collections.addAll(hitList, entries);
|
||||
|
||||
return hitList;
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3098,71 +3062,51 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
* Create a new PlatformConfig based of the one held but with overridden properties by reading
|
||||
* properties with the given path and prefix.
|
||||
* <p>
|
||||
* Typically used in Db Migration generation for many platform targets that might have different
|
||||
* configuration for IdType, UUID, quoted identifiers etc.
|
||||
* </p>
|
||||
*
|
||||
* @param propertiesPath The properties path used for loading and setting properties
|
||||
* @param platformPrefix The prefix used for loading and setting properties
|
||||
* @return A copy of the PlatformConfig with overridden properties
|
||||
*/
|
||||
public enum DbUuid {
|
||||
public PlatformConfig newPlatformConfig(String propertiesPath, String platformPrefix) {
|
||||
|
||||
PropertiesWrapper p = new PropertiesWrapper(propertiesPath, platformPrefix, properties, classLoadConfig);
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
PlatformConfig config = new PlatformConfig(platformConfig);
|
||||
config.loadSettings(p);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
/**
|
||||
* Add a mapping location to search for xml mapping via class path search.
|
||||
*/
|
||||
public void addMappingLocation(String mappingLocation) {
|
||||
if (mappingLocations == null) {
|
||||
mappingLocations = new ArrayList<>();
|
||||
}
|
||||
mappingLocations.add(mappingLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if native UUID type is preferred.
|
||||
*/
|
||||
public boolean useNativeType() {
|
||||
return nativeType;
|
||||
}
|
||||
/**
|
||||
* Return mapping locations to search for xml mapping via class path search.
|
||||
*/
|
||||
public List<String> getMappingLocations() {
|
||||
return mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
|
||||
*/
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
/**
|
||||
* Set mapping locations to search for xml mapping via class path search.
|
||||
* <p>
|
||||
* This is only used if classes have not been explicitly specified.
|
||||
* </p>
|
||||
*/
|
||||
public void setMappingLocations(List<String> mappingLocations) {
|
||||
this.mappingLocations = mappingLocations;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
|
||||
@@ -112,39 +112,7 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
* Convert and return the string to underscore from camel case.
|
||||
*/
|
||||
protected String toUnderscoreFromCamel(String camelCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
if ('_' == c) {
|
||||
// Underscores should just be passed through
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
lastUpper = i;
|
||||
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (forceUpperCase) {
|
||||
ret = ret.toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
return CamelCaseHelper.toUnderscoreFromCamel(camelCase, digitsCompressed, forceUpperCase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,7 @@ import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.CustomDbTypeMapping;
|
||||
import io.ebean.config.DbTypeConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -215,16 +214,16 @@ public class DatabasePlatform {
|
||||
/**
|
||||
* Configure the platform given the server configuration.
|
||||
*/
|
||||
public void configure(ServerConfig config) {
|
||||
public void configure(PlatformConfig config) {
|
||||
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
|
||||
configureIdType(config.getIdType());
|
||||
configure(config.getDbTypeConfig(), config.isAllQuotedIdentifiers());
|
||||
configure(config, config.isAllQuotedIdentifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure UUID Storage etc based on ServerConfig settings.
|
||||
*/
|
||||
protected void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
|
||||
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
addGeoTypes(config.getGeometrySRID());
|
||||
configureIdType(config.getIdType());
|
||||
@@ -382,6 +381,13 @@ public class DatabasePlatform {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* So no except for Postgres and CockroachDB.
|
||||
*/
|
||||
public boolean isNativeArrayType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB supports native UUID.
|
||||
*/
|
||||
@@ -518,6 +524,13 @@ public class DatabasePlatform {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
*/
|
||||
public boolean isDdlCommitOnCreateIndex() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
@@ -119,8 +119,8 @@ public class DbPlatformTypeMapping {
|
||||
put(DbType.JSONCLOB, JSON_CLOB_PLACEHOLDER);
|
||||
put(DbType.JSONBLOB, JSON_BLOB_PLACEHOLDER);
|
||||
put(DbType.JSONVARCHAR, JSON_VARCHAR_PLACEHOLDER);
|
||||
// use reasonable default of varchar(40) - ideally set via DatabasePlatform.configure(DbTypeConfig)
|
||||
put(DbType.UUID, get(DbType.VARCHAR).withLength(40));
|
||||
// default to native UUID and override on platform configure()
|
||||
put(DbType.UUID, UUID_NATIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +198,9 @@ public class DbPlatformTypeMapping {
|
||||
/**
|
||||
* Map the UUID appropriately based on native DB support and ServerConfig.DbUuid.
|
||||
*/
|
||||
public void config(boolean nativeUuidType, ServerConfig.DbUuid dbUuid) {
|
||||
public void config(boolean nativeUuidType, PlatformConfig.DbUuid dbUuid) {
|
||||
if (nativeUuidType && dbUuid.useNativeType()) {
|
||||
put(DbType.UUID, UUID_NATIVE);
|
||||
// native UUID already set by default
|
||||
} else if (dbUuid.useBinary()) {
|
||||
put(DbType.UUID, get(DbType.BINARY).withLength(16));
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebean.config.dbplatform.cockroach;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
|
||||
/**
|
||||
* CockroachDB based platform.
|
||||
*/
|
||||
public class CockroachPlatform extends PostgresPlatform {
|
||||
|
||||
public CockroachPlatform() {
|
||||
super();
|
||||
this.platform = Platform.COCKROACH;
|
||||
// no like escape clause supported
|
||||
this.likeSpecialCharacters = new char[]{'%', '_'};
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.likeClauseEscaped = "like ?";
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs a commit after create index such that alter table add foreign key ... succeeds.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDdlCommitOnCreateIndex() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,6 +88,14 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
return new DbPlatformType("geometry(" + type + "," + srid + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* So we can generate varchar[], int[], uuid[] column definitions and use the associated scalar types.
|
||||
*/
|
||||
@Override
|
||||
public boolean isNativeArrayType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform that has non-UTF8 types (char, varchar, text) and default to Identity rather than Sequence.
|
||||
*/
|
||||
public class SqlServer16Platform extends SqlServerBasePlatform {
|
||||
|
||||
public SqlServer16Platform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER16;
|
||||
// default to use Identity rather than sequences
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
|
||||
// non-utf8 column types
|
||||
dbTypeMap.put(DbType.CHAR, new DbPlatformType("char", 1));
|
||||
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("varchar", 255));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform - NVarchar UTF types and Sequence preference.
|
||||
*/
|
||||
public class SqlServer17Platform extends SqlServerBasePlatform {
|
||||
|
||||
public SqlServer17Platform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER17;
|
||||
this.columnAliasPrefix = null;
|
||||
this.nativeUuidType = true;
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("uniqueidentifier", false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package io.ebean.config.dbplatform.sqlserver;
|
||||
/**
|
||||
* SQL Server platform using the older ROW_NUMBER() mechanism.
|
||||
*/
|
||||
public class SqlServer2005Platform extends SqlServerPlatform {
|
||||
public class SqlServer2005Platform extends SqlServer16Platform {
|
||||
|
||||
public SqlServer2005Platform() {
|
||||
this.sqlLimiter = new SqlServer2005SqlLimiter();
|
||||
|
||||
+14
-10
@@ -4,7 +4,7 @@ import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
@@ -16,11 +16,11 @@ import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform.
|
||||
* Base Microsoft SQL Server platform - NVarchar UTF types and Sequence preference by default.
|
||||
*/
|
||||
public class SqlServerPlatform extends DatabasePlatform {
|
||||
abstract class SqlServerBasePlatform extends DatabasePlatform {
|
||||
|
||||
public SqlServerPlatform() {
|
||||
SqlServerBasePlatform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER;
|
||||
// disable persistBatchOnCascade mode for
|
||||
@@ -31,7 +31,7 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
this.sqlLimiter = new SqlServerSqlLimiter();
|
||||
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
|
||||
this.historySupport = new SqlServerHistorySupport();
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
@@ -63,22 +63,26 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("numeric", 28));
|
||||
|
||||
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
|
||||
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
|
||||
|
||||
dbTypeMap.put(DbType.DATE, new DbPlatformType("date"));
|
||||
dbTypeMap.put(DbType.TIME, new DbPlatformType("time"));
|
||||
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime2"));
|
||||
|
||||
// UTF8 aware types - overwritten in SqlServer16 platform
|
||||
dbTypeMap.put(DbType.CHAR, new DbPlatformType("nchar", 1));
|
||||
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
|
||||
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
|
||||
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig config) {
|
||||
public void configure(PlatformConfig config) {
|
||||
super.configure(config);
|
||||
if (dbIdentity.getIdType() == IdType.SEQUENCE) {
|
||||
this.persistBatchOnCascade = PersistBatch.ALL;
|
||||
@@ -56,17 +56,6 @@ public class PropertiesLoader {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (override) a configuration property.
|
||||
*
|
||||
* This is expected to be only called by test configuration logic
|
||||
* (ala automatic test configuration via ebean-test-config).
|
||||
*/
|
||||
public static synchronized void setProperty(String key, String value) {
|
||||
load();
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the properties with 'eval' run on all the values.
|
||||
* This resolves expressions like ${HOME} etc.
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* An abstract MetricVisitor that handles the boolean flags - reset, collectTransactionMetrics and collectQueryMetrics.
|
||||
*/
|
||||
public abstract class AbstractMetricVisitor implements MetricVisitor {
|
||||
|
||||
private final boolean reset;
|
||||
private final boolean collectTransactionMetrics;
|
||||
private final boolean collectQueryMetrics;
|
||||
|
||||
public AbstractMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
|
||||
this.reset = reset;
|
||||
this.collectTransactionMetrics = collectTransactionMetrics;
|
||||
this.collectQueryMetrics = collectQueryMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReset() {
|
||||
return reset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectTransactionMetrics() {
|
||||
return collectTransactionMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectQueryMetrics() {
|
||||
return collectQueryMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitStart() {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
// do nothing by default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple MetricVisitor that can collect the desired metrics into lists.
|
||||
*/
|
||||
public class BasicMetricVisitor extends AbstractMetricVisitor {
|
||||
|
||||
private final List<MetaTimedMetric> timed = new ArrayList<>();
|
||||
private final List<MetaQueryMetric> dtoQuery = new ArrayList<>();
|
||||
private final List<MetaOrmQueryMetric> ormQuery = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct to reset and collect everything.
|
||||
*/
|
||||
public BasicMetricVisitor() {
|
||||
super(true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct specifying reset and what to collect.
|
||||
*/
|
||||
public BasicMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
|
||||
super(reset, collectTransactionMetrics, collectQueryMetrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
|
||||
*/
|
||||
public List<MetaTimedMetric> getTimedMetrics() {
|
||||
return timed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DTO query metrics.
|
||||
*/
|
||||
public List<MetaQueryMetric> getDtoQueryMetrics() {
|
||||
return dtoQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ORM query metrics.
|
||||
*/
|
||||
public List<MetaOrmQueryMetric> getOrmQueryMetrics() {
|
||||
return ormQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTimed(MetaTimedMetric metric) {
|
||||
timed.add(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitQuery(MetaQueryMetric metric) {
|
||||
dtoQuery.add(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOrmQuery(MetaOrmQueryMetric metric) {
|
||||
ormQuery.add(metric);
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,20 @@ import java.util.List;
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Collect and return the transaction execution metrics.
|
||||
* Visit the metrics resetting and collecting/reporting as desired.
|
||||
*/
|
||||
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Collect query plan statistics (new, will migrate ORM query stats over to this).
|
||||
* Run a visit collecting all the metrics and returning BasicMetricVisitor
|
||||
* which holds all the metrics in simple lists.
|
||||
*/
|
||||
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
|
||||
BasicMetricVisitor visitBasic();
|
||||
|
||||
/**
|
||||
* Collect and return the non-empty query plan statistics for all the beans.
|
||||
* <p>
|
||||
* Note that this excludes the query plan statistics where there has been no
|
||||
* executions (since the last collection with reset).
|
||||
* </p>
|
||||
* Just reset all the metrics. Maybe only useful for testing purposes.
|
||||
*/
|
||||
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
|
||||
void resetAllMetrics();
|
||||
|
||||
/**
|
||||
* Collect and return the ObjectGraphNode statistics.
|
||||
@@ -36,6 +33,6 @@ public interface MetaInfoManager {
|
||||
*
|
||||
* @param reset Set to true to reset the underlying statistics after collection.
|
||||
*/
|
||||
List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset);
|
||||
List<MetaOrmQueryNode> collectNodeStatistics(boolean reset);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Query execution statistics for Orm queries.
|
||||
*/
|
||||
public interface MetaOrmQueryMetric extends MetaQueryMetric {
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this query plan was tuned by AutoTune.
|
||||
*/
|
||||
boolean isAutoTuned();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
/**
|
||||
* Return the time of the last query executed using this plan.
|
||||
*/
|
||||
long getLastQueryTime();
|
||||
|
||||
/**
|
||||
* Return the 'origin' points and paths that resulted in the query being
|
||||
* executed and the associated number of times the query was executed via that
|
||||
* path.
|
||||
* <p>
|
||||
* This includes direct and lazy loading paths.
|
||||
* </p>
|
||||
*/
|
||||
List<MetaOrmQueryOrigin> getOrigins();
|
||||
|
||||
}
|
||||
+1
-3
@@ -8,10 +8,8 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
* These statistics can be used to identify origin queries that result in lots
|
||||
* of lazy loading.
|
||||
* </p>
|
||||
*
|
||||
* @see MetaInfoManager#collectNodeStatistics(boolean)
|
||||
*/
|
||||
public interface MetaObjectGraphNodeStats {
|
||||
public interface MetaOrmQueryNode {
|
||||
|
||||
/**
|
||||
* Return the ObjectGraphNode which has the origin point and relative path.
|
||||
+1
-4
@@ -9,11 +9,8 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
* This basically points to the bit of original code and query that results in
|
||||
* this query directly or via lazy loading.
|
||||
* </p>
|
||||
*
|
||||
* @see MetaQueryPlanStatistic
|
||||
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
|
||||
*/
|
||||
public interface MetaQueryPlanOriginCount {
|
||||
public interface MetaOrmQueryOrigin {
|
||||
|
||||
/**
|
||||
* The 'origin' and path which this query belongs to.
|
||||
@@ -1,98 +0,0 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Query execution statistics Meta data.
|
||||
*
|
||||
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
|
||||
*/
|
||||
public interface MetaQueryPlanStatistic {
|
||||
|
||||
/**
|
||||
* Return the bean type this query plan is for.
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this query plan was tuned by AutoTune.
|
||||
*/
|
||||
boolean isAutoTuned();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
/**
|
||||
* Return the sql executed.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
/**
|
||||
* Return the total number of queries executed.
|
||||
*/
|
||||
long getExecutionCount();
|
||||
|
||||
/**
|
||||
* Return the total number of beans loaded by the queries.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getTotalLoadedBeans();
|
||||
|
||||
/**
|
||||
* Return the total time taken by executions of this query.
|
||||
*/
|
||||
long getTotalTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the max execution time for this query.
|
||||
*/
|
||||
long getMaxTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the time collection started (or was last reset).
|
||||
*/
|
||||
long getCollectionStart();
|
||||
|
||||
/**
|
||||
* Return the time of the last query executed using this plan.
|
||||
*/
|
||||
long getLastQueryTime();
|
||||
|
||||
/**
|
||||
* Return the average query execution time in microseconds.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getAvgTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the average number of bean loaded per query.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getAvgLoadedBeans();
|
||||
|
||||
/**
|
||||
* Return the 'origin' points and paths that resulted in the query being
|
||||
* executed and the associated number of times the query was executed via that
|
||||
* path.
|
||||
* <p>
|
||||
* This includes direct and lazy loading paths.
|
||||
* </p>
|
||||
*/
|
||||
List<MetaQueryPlanOriginCount> getOrigins();
|
||||
|
||||
}
|
||||
@@ -6,6 +6,11 @@ package io.ebean.meta;
|
||||
*/
|
||||
public interface MetaTimedMetric {
|
||||
|
||||
/**
|
||||
* Return the metric type.
|
||||
*/
|
||||
MetricType getMetricType();
|
||||
|
||||
/**
|
||||
* Return the metric name.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* The type of Metric.
|
||||
*/
|
||||
public enum MetricType {
|
||||
|
||||
/**
|
||||
* Transactions.
|
||||
*/
|
||||
TXN,
|
||||
|
||||
/**
|
||||
* ORM queries.
|
||||
*/
|
||||
ORM,
|
||||
|
||||
/**
|
||||
* DTO queries.
|
||||
*/
|
||||
DTO,
|
||||
|
||||
/**
|
||||
* SQL queries with a label will have metrics collected.
|
||||
* <p>
|
||||
* SqlQuery and SqlUpdate without a label have no metrics collected.
|
||||
*/
|
||||
SQL
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* Defines visitor to read and report the transaction and query metrics.
|
||||
*/
|
||||
public interface MetricVisitor {
|
||||
|
||||
/**
|
||||
* Return true if the metrics should be reset.
|
||||
*/
|
||||
boolean isReset();
|
||||
|
||||
/**
|
||||
* Return true if we should visit the transaction metrics.
|
||||
*/
|
||||
boolean isCollectTransactionMetrics();
|
||||
|
||||
/**
|
||||
* Return true if we should visit the ORM and SQL query metrics.
|
||||
*/
|
||||
boolean isCollectQueryMetrics();
|
||||
|
||||
/**
|
||||
* Visit has started.
|
||||
*/
|
||||
void visitStart();
|
||||
|
||||
/**
|
||||
* Visit transaction metrics (and L2 cache metrics in future).
|
||||
*/
|
||||
void visitTimed(MetaTimedMetric metric);
|
||||
|
||||
/**
|
||||
* Visit DTO and SQL query metrics.
|
||||
*/
|
||||
void visitQuery(MetaQueryMetric metric);
|
||||
|
||||
/**
|
||||
* Visit ORM query metrics.
|
||||
*/
|
||||
void visitOrmQuery(MetaOrmQueryMetric metric);
|
||||
|
||||
/**
|
||||
* Visit has completed.
|
||||
*/
|
||||
void visitEnd();
|
||||
|
||||
}
|
||||
@@ -2,6 +2,52 @@ package io.ebean.util;
|
||||
|
||||
public class CamelCaseHelper {
|
||||
|
||||
/**
|
||||
* To underscore from camel case using digits compressed true and force upper case false.
|
||||
*/
|
||||
public static String toUnderscoreFromCamel(String camelCase) {
|
||||
return toUnderscoreFromCamel(camelCase, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert and return the string to underscore from camel case.
|
||||
*/
|
||||
public static String toUnderscoreFromCamel(String camelCase, boolean digitsCompressed, boolean forceUpperCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
if ('_' == c) {
|
||||
// Underscores should just be passed through
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
lastUpper = i;
|
||||
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (forceUpperCase) {
|
||||
ret = ret.toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* To camel from underscore.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.util.StringHelper;
|
||||
@@ -124,11 +123,11 @@ public class LoadManyRequest extends LoadRequest {
|
||||
return loadContext.getBeanProperty();
|
||||
}
|
||||
|
||||
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
|
||||
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
|
||||
|
||||
BeanPropertyAssocMany<?> many = getMany();
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
|
||||
SpiQuery<?> query = many.newQuery(server);
|
||||
String orderBy = many.getLazyFetchOrderBy();
|
||||
if (orderBy != null) {
|
||||
query.orderBy(orderBy);
|
||||
|
||||
@@ -21,6 +21,11 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
|
||||
private ScopeTrans current;
|
||||
|
||||
/**
|
||||
* Flag set when we clear the thread scope (on commit/rollback or end).
|
||||
*/
|
||||
private boolean scopeCleared;
|
||||
|
||||
public ScopedTransaction(TransactionScopeManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
@@ -47,30 +52,51 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
*/
|
||||
public void complete(Object returnOrThrowable, int opCode) {
|
||||
current.complete(returnOrThrowable, opCode);
|
||||
// no finally here for pop() as we come in here twice if an
|
||||
// error is thrown on commit (due to enhancement finally block)
|
||||
pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatic complete - finally block, try to commit.
|
||||
* Internal programmatic complete - finally block, try to commit.
|
||||
*/
|
||||
public void complete() {
|
||||
current.complete();
|
||||
pop();
|
||||
try {
|
||||
current.complete();
|
||||
} finally {
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearScopeOnce() {
|
||||
if (!scopeCleared) {
|
||||
manager.set(null);
|
||||
scopeCleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clearScope() {
|
||||
if (stack.isEmpty()) {
|
||||
clearScopeOnce();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void pop() {
|
||||
if (!stack.isEmpty()) {
|
||||
if (!clearScope()) {
|
||||
current = stack.pop();
|
||||
transaction = current.getTransaction();
|
||||
} else {
|
||||
manager.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() throws PersistenceException {
|
||||
current.end();
|
||||
pop();
|
||||
try {
|
||||
current.end();
|
||||
} finally {
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,17 +106,29 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
current.commitTransaction();
|
||||
try {
|
||||
current.commitTransaction();
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws PersistenceException {
|
||||
current.rollback(null);
|
||||
try {
|
||||
current.rollback(null);
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(Throwable e) throws PersistenceException {
|
||||
current.rollback(e);
|
||||
try {
|
||||
current.rollback(e);
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.server.core.SpiResultSet;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
@@ -257,4 +258,14 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
|
||||
*/
|
||||
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Visit all the metrics (typically reporting them).
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Return true if a row for the bean type and id exists.
|
||||
*/
|
||||
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.core.DbExpressionHandler;
|
||||
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.text.json.JsonWriteOptions;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
@@ -21,4 +23,10 @@ public interface SpiJsonContext extends JsonContext {
|
||||
* Create a Json Writer for writing beans as JSON supplying a writer.
|
||||
*/
|
||||
SpiJsonWriter createJsonWriter(Writer writer);
|
||||
|
||||
/**
|
||||
* Create a Json Reader for reading the JSON content.
|
||||
*/
|
||||
SpiJsonReader createJsonRead(BeanType<?> beanType, String json);
|
||||
|
||||
}
|
||||
|
||||
@@ -179,6 +179,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return the label set on the query.
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,11 @@ public interface SpiSqlBinding {
|
||||
*/
|
||||
String getQuery();
|
||||
|
||||
/**
|
||||
* Return the label (to collect metrics on when set).
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the first row to fetch.
|
||||
*/
|
||||
|
||||
@@ -23,4 +23,9 @@ public interface SpiSqlUpdate extends SqlUpdate {
|
||||
* Set the generated key value.
|
||||
*/
|
||||
void setGeneratedKey(Object idValue);
|
||||
|
||||
/**
|
||||
* Reset bind position to be ready for another bind execute.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ public interface SpiUpdate<T> extends Update<T> {
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the label (for metrics collection).
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the type of this - insert, update or delete.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ebeaninternal.api.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SpiJsonReader {
|
||||
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
SpiJsonReader forJson(JsonParser moreJson, boolean resetContext);
|
||||
|
||||
<T> void persistenceContextPut(Object beanId, T currentBean);
|
||||
|
||||
Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc);
|
||||
|
||||
ObjectMapper getObjectMapper();
|
||||
|
||||
JsonParser getParser();
|
||||
|
||||
JsonToken nextToken() throws IOException;
|
||||
|
||||
void pushPath(String path);
|
||||
|
||||
void popPath();
|
||||
|
||||
void beanVisitor(Object bean, Map<String, Object> unmappedProperties);
|
||||
|
||||
Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;
|
||||
}
|
||||
+8
-2
@@ -1,9 +1,10 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
package io.ebeaninternal.api.json;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.json.JsonWriter;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,11 @@ import java.util.Collection;
|
||||
*/
|
||||
public interface SpiJsonWriter extends JsonWriter {
|
||||
|
||||
/**
|
||||
* Flush the buffer.
|
||||
*/
|
||||
void flush() throws IOException;
|
||||
|
||||
/**
|
||||
* Return true if the value is a parent bean.
|
||||
*/
|
||||
@@ -60,7 +66,7 @@ public interface SpiJsonWriter extends JsonWriter {
|
||||
* Write value using underlying Jaskson object mapper if available.
|
||||
*/
|
||||
void writeValueUsingObjectMapper(String name, Object value);
|
||||
|
||||
|
||||
/**
|
||||
* Write the bean properties.
|
||||
*/
|
||||
@@ -37,6 +37,7 @@ public class DdlGenerator {
|
||||
private final boolean runDdl;
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
private final boolean ddlCommitOnCreateIndex;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -50,8 +51,10 @@ public class DdlGenerator {
|
||||
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
|
||||
this.runDdl = false;
|
||||
this.ddlCommitOnCreateIndex = false;
|
||||
} else {
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +112,8 @@ public class DdlGenerator {
|
||||
try {
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(true);
|
||||
} else if (ddlCommitOnCreateIndex) {
|
||||
runner.setCommitOnCreateIndex();
|
||||
}
|
||||
int count = runner.runAll(content, connection);
|
||||
if (expectErrors) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.EbeanServer;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
@@ -15,7 +16,8 @@ import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
|
||||
import io.ebean.dbmigration.DbMigration;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -226,15 +228,17 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (!online) {
|
||||
DbOffline.setGenerateMigration();
|
||||
if (databasePlatform == null && !platforms.isEmpty()) {
|
||||
// for multiple platform generation set the general platform
|
||||
// to H2 so that it runs offline without DB connection
|
||||
// for multiple platform generation the first platform
|
||||
// is used to generate the "logical" model diff
|
||||
setPlatform(platforms.get(0).platform);
|
||||
}
|
||||
}
|
||||
setDefaults();
|
||||
if (!platforms.isEmpty()) {
|
||||
configurePlatforms();
|
||||
}
|
||||
try {
|
||||
Request request = createRequest();
|
||||
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform);
|
||||
}
|
||||
@@ -253,6 +257,16 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration for each of the target platforms.
|
||||
*/
|
||||
private void configurePlatforms() {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformConfig config = serverConfig.newPlatformConfig("dbmigration.platform", pair.prefix);
|
||||
pair.platform.configure(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate "repeatable" migration scripts.
|
||||
* <p>
|
||||
@@ -554,8 +568,12 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return new OraclePlatform();
|
||||
case SQLANYWHERE:
|
||||
return new SqlAnywherePlatform();
|
||||
case SQLSERVER16:
|
||||
return new SqlServer16Platform();
|
||||
case SQLSERVER17:
|
||||
return new SqlServer17Platform();
|
||||
case SQLSERVER:
|
||||
return new SqlServerPlatform();
|
||||
throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details");
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
/**
|
||||
* CockroachDB specific DDL handling.
|
||||
*/
|
||||
public class CockroachDdl extends PlatformDdl {
|
||||
|
||||
public CockroachDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
this.columnSetNull = "drop not null";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
return NativeDbArray.logicalToNative(logicalArrayType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map bigint, integer and smallint all into serial.
|
||||
*/
|
||||
@Override
|
||||
public String asIdentityColumn(String columnDefn) {
|
||||
|
||||
if ("bigint".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("integer".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("smallint".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInlineComments() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
/**
|
||||
* Helper for logical type conversion.
|
||||
*/
|
||||
class NativeDbArray {
|
||||
|
||||
/**
|
||||
* Covert the 'logical' array type to a native one (for Postgres and Cockroach).
|
||||
*/
|
||||
static String logicalToNative(String logicalArrayType) {
|
||||
int colonPos = logicalArrayType.lastIndexOf(']');
|
||||
if (colonPos == -1) {
|
||||
return logicalArrayType;
|
||||
} else {
|
||||
// trim of the fallback varchar length
|
||||
return logicalArrayType.substring(0, colonPos + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,7 @@ public class PostgresDdl extends PlatformDdl {
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
int colonPos = logicalArrayType.lastIndexOf(']');
|
||||
if (colonPos == -1) {
|
||||
return logicalArrayType;
|
||||
} else {
|
||||
// trim of the fallback varchar length
|
||||
return logicalArrayType.substring(0, colonPos + 1);
|
||||
}
|
||||
return NativeDbArray.logicalToNative(logicalArrayType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,13 +4,18 @@ import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbPlatformTypeMapping;
|
||||
import io.ebeaninternal.dbmigration.model.MColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebeaninternal.dbmigration.model.ModelContainer;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
import io.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* The context used during DDL generation.
|
||||
@@ -187,4 +192,49 @@ public class ModelBuildContext {
|
||||
addTable(draftTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a builder to add foreign keys.
|
||||
*/
|
||||
public FkeyBuilder fkeyBuilder(MTable destTable) {
|
||||
return new FkeyBuilder(this, destTable);
|
||||
}
|
||||
|
||||
public static class FkeyBuilder {
|
||||
|
||||
private final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
private final ModelBuildContext ctx;
|
||||
|
||||
private final MTable destTable;
|
||||
|
||||
private final String tableName;
|
||||
|
||||
FkeyBuilder(ModelBuildContext ctx, MTable destTable) {
|
||||
this.ctx = ctx;
|
||||
this.destTable = destTable;
|
||||
this.tableName = destTable.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a foreign key based on the table join.
|
||||
*/
|
||||
public FkeyBuilder addForeignKey(BeanDescriptor<?> desc, TableJoin tableJoin, boolean direction) {
|
||||
|
||||
String baseTable = ctx.normaliseTable(desc.getBaseTable());
|
||||
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet());
|
||||
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get());
|
||||
|
||||
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
|
||||
|
||||
for (TableJoinColumn column : tableJoin.columns()) {
|
||||
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
|
||||
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
|
||||
foreignKey.addColumnPair(localCol, refCol);
|
||||
}
|
||||
|
||||
destTable.addForeignKey(foreignKey);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package io.ebeaninternal.dbmigration.model.build;
|
||||
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.BeanTable;
|
||||
|
||||
/**
|
||||
* Add the element collection table to the model.
|
||||
*/
|
||||
public class ModelBuildElementTable {
|
||||
|
||||
/**
|
||||
* Build and add the MTable model for the ElementCollection property.
|
||||
*/
|
||||
public static void build(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
|
||||
|
||||
BeanTable beanTable = manyProp.getBeanTable();
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = manyProp.getTargetDescriptor();
|
||||
|
||||
MTable table = new MTable(beanTable.getBaseTable());
|
||||
|
||||
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
|
||||
|
||||
ctx.fkeyBuilder(table)
|
||||
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
|
||||
|
||||
ctx.addTable(table);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+5
-28
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.dbmigration.model.build;
|
||||
|
||||
import io.ebeaninternal.dbmigration.model.MColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -12,7 +11,7 @@ import io.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
/**
|
||||
* Add the intersection table to the model.
|
||||
*/
|
||||
public class ModelBuildIntersectionTable {
|
||||
class ModelBuildIntersectionTable {
|
||||
|
||||
private final ModelBuildContext ctx;
|
||||
|
||||
@@ -22,9 +21,7 @@ public class ModelBuildIntersectionTable {
|
||||
|
||||
private MTable intersectionTable;
|
||||
|
||||
private int countForeignKey;
|
||||
|
||||
public ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
|
||||
ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
|
||||
this.ctx = ctx;
|
||||
this.manyProp = manyProp;
|
||||
this.intersectionTableJoin = manyProp.getIntersectionTableJoin();
|
||||
@@ -51,33 +48,13 @@ public class ModelBuildIntersectionTable {
|
||||
|
||||
private void buildFkConstraints() {
|
||||
|
||||
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
|
||||
buildFkConstraints(localDesc, intersectionTableJoin.columns(), true);
|
||||
|
||||
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
|
||||
buildFkConstraints(targetDesc, tableJoin.columns(), false);
|
||||
ctx.fkeyBuilder(intersectionTable)
|
||||
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
|
||||
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
|
||||
|
||||
intersectionTable.checkDuplicateForeignKeys();
|
||||
}
|
||||
|
||||
|
||||
private void buildFkConstraints(BeanDescriptor<?> desc, TableJoinColumn[] columns, boolean direction) {
|
||||
|
||||
String tableName = intersectionTableJoin.getTable();
|
||||
String baseTable = ctx.normaliseTable(desc.getBaseTable());
|
||||
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey);
|
||||
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey);
|
||||
|
||||
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
|
||||
intersectionTable.addForeignKey(foreignKey);
|
||||
|
||||
for (TableJoinColumn column : columns) {
|
||||
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
|
||||
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
|
||||
foreignKey.addColumnPair(localCol, refCol);
|
||||
}
|
||||
}
|
||||
|
||||
private MTable createTable() {
|
||||
|
||||
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
|
||||
|
||||
+15
-18
@@ -123,27 +123,24 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMany(BeanPropertyAssocMany<?> p) {
|
||||
if (p.hasJoinTable()) {
|
||||
if (p.getMappedBy() == null) {
|
||||
// only create on other 'owning' side
|
||||
if (p.hasJoinTable() && p.getMappedBy() == null) {
|
||||
// only create on other 'owning' side
|
||||
|
||||
//TableJoin intersectionTableJoin = p.getIntersectionTableJoin();
|
||||
// check if the intersection table has already been created
|
||||
|
||||
// build the create table and fkey constraints
|
||||
// putting the DDL into ctx for later output as we are
|
||||
// in the middle of rendering the create table DDL
|
||||
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
|
||||
if (p.isO2mJoinTable()) {
|
||||
intersectionTable.clearForeignKeyIndexes();
|
||||
Collection<MColumn> cols = intersectionTable.allColumns();
|
||||
if (cols.size() == 2) {
|
||||
// always the second column that we put the unique constraint on
|
||||
MColumn col = new ArrayList<>(cols).get(1);
|
||||
col.setUnique(determineUniqueConstraintName(col.getName()));
|
||||
}
|
||||
// build the create table and fkey constraints
|
||||
// putting the DDL into ctx for later output as we are
|
||||
// in the middle of rendering the create table DDL
|
||||
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
|
||||
if (p.isO2mJoinTable()) {
|
||||
intersectionTable.clearForeignKeyIndexes();
|
||||
Collection<MColumn> cols = intersectionTable.allColumns();
|
||||
if (cols.size() == 2) {
|
||||
// always the second column that we put the unique constraint on
|
||||
MColumn col = new ArrayList<>(cols).get(1);
|
||||
col.setUnique(determineUniqueConstraintName(col.getName()));
|
||||
}
|
||||
}
|
||||
} else if (p.isElementCollection()) {
|
||||
ModelBuildElementTable.build(ctx, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,14 @@ import java.util.List;
|
||||
*/
|
||||
public class VisitAllUsing {
|
||||
|
||||
/**
|
||||
* Visit a single Descriptor using the given visitor.
|
||||
*/
|
||||
public static void visitOne(BeanDescriptor<?> descriptor, BeanPropertyVisitor visitor) {
|
||||
|
||||
new VisitAllUsing().visitProperties(descriptor, visitor);
|
||||
}
|
||||
|
||||
protected final BeanVisitor visitor;
|
||||
|
||||
protected final List<BeanDescriptor<?>> descriptors;
|
||||
@@ -36,6 +44,11 @@ public class VisitAllUsing {
|
||||
this.descriptors = descriptors;
|
||||
}
|
||||
|
||||
private VisitAllUsing() {
|
||||
this.visitor = null;
|
||||
this.descriptors = null;
|
||||
}
|
||||
|
||||
public void visitAllBeans() {
|
||||
for (BeanDescriptor<?> desc : descriptors) {
|
||||
if (desc.isBaseTable()) {
|
||||
@@ -51,29 +64,32 @@ public class VisitAllUsing {
|
||||
|
||||
BeanPropertyVisitor propertyVisitor = visitor.visitBean(desc);
|
||||
if (propertyVisitor != null) {
|
||||
|
||||
BeanProperty idProp = desc.getIdProperty();
|
||||
if (idProp != null) {
|
||||
visit(propertyVisitor, idProp);
|
||||
}
|
||||
|
||||
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
|
||||
if (unidirectional != null) {
|
||||
visit(propertyVisitor, unidirectional);
|
||||
}
|
||||
|
||||
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
|
||||
for (BeanProperty p : propertiesNonTransient) {
|
||||
if (p.isDDLColumn()) {
|
||||
visit(propertyVisitor, p);
|
||||
}
|
||||
}
|
||||
|
||||
visitInheritanceProperties(desc, propertyVisitor);
|
||||
propertyVisitor.visitEnd();
|
||||
visitProperties(desc, propertyVisitor);
|
||||
}
|
||||
}
|
||||
|
||||
private void visitProperties(BeanDescriptor<?> desc, BeanPropertyVisitor propertyVisitor) {
|
||||
BeanProperty idProp = desc.getIdProperty();
|
||||
if (idProp != null) {
|
||||
visit(propertyVisitor, idProp);
|
||||
}
|
||||
|
||||
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
|
||||
if (unidirectional != null) {
|
||||
visit(propertyVisitor, unidirectional);
|
||||
}
|
||||
|
||||
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
|
||||
for (BeanProperty p : propertiesNonTransient) {
|
||||
if (p.isDDLColumn()) {
|
||||
visit(propertyVisitor, p);
|
||||
}
|
||||
}
|
||||
|
||||
visitInheritanceProperties(desc, propertyVisitor);
|
||||
propertyVisitor.visitEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit the property.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -16,6 +17,8 @@ public class ExtraDdlXmlReader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
|
||||
|
||||
private static final String SQLSERVER = Platform.SQLSERVER.name().toLowerCase();
|
||||
|
||||
/**
|
||||
* Return the combined extra DDL that should be run given the platform name.
|
||||
*/
|
||||
@@ -52,14 +55,33 @@ public class ExtraDdlXmlReader {
|
||||
return true;
|
||||
}
|
||||
|
||||
String genericMatch = genericPlatformMatch(platformName);
|
||||
|
||||
for (String name : StringHelper.splitNames(platforms)) {
|
||||
if (name.toLowerCase().contains(platformName)) {
|
||||
return true;
|
||||
} else if (genericMatch != null && genericMatch.equals(name.toLowerCase())) {
|
||||
// allow sqlserver ... to match sqlserver17 and sqlserver16 platforms
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "generic" platform name that can be used. e.g. sqlserver17 -> sqlserver.
|
||||
*/
|
||||
private static String genericPlatformMatch(String platformName) {
|
||||
switch (platformName) {
|
||||
case "sqlserver17":
|
||||
return SQLSERVER;
|
||||
case "sqlserver16":
|
||||
return SQLSERVER;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a ExtraDdl from an xml document at the given resource path.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
|
||||
/**
|
||||
* Factory to create timed metric counters.
|
||||
*/
|
||||
@@ -15,20 +17,16 @@ public interface MetricFactory {
|
||||
/**
|
||||
* Create a timed metric group.
|
||||
*/
|
||||
TimedMetricMap createTimedMetricMap(String name);
|
||||
TimedMetricMap createTimedMetricMap(MetricType metricType, String name);
|
||||
|
||||
/**
|
||||
* Create a Timed metric.
|
||||
*/
|
||||
TimedMetric createTimedMetric(String name);
|
||||
TimedMetric createTimedMetric(MetricType metricType, String name);
|
||||
|
||||
/**
|
||||
* Create a Timed metric.
|
||||
*/
|
||||
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql);
|
||||
QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql);
|
||||
|
||||
/**
|
||||
* Return a instance used to collect Query plan metrics.
|
||||
*/
|
||||
QueryPlanCollector createCollector(boolean reset);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
/**
|
||||
* Internal Query plan metric holder.
|
||||
*/
|
||||
@@ -11,7 +13,7 @@ public interface QueryPlanMetric {
|
||||
TimedMetric getMetric();
|
||||
|
||||
/**
|
||||
* Collect the non-empty query plan metrics.
|
||||
* Visit the underlying metric.
|
||||
*/
|
||||
void collect(QueryPlanCollector collector);
|
||||
void visit(MetricVisitor visitor);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
import java.util.List;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
/**
|
||||
* Metric for timed events like transaction execution times.
|
||||
@@ -25,12 +23,17 @@ public interface TimedMetric {
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Collect the timed metric statistics.
|
||||
* Reset the statistics.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Collect and return a snapshot of the metrics.
|
||||
*/
|
||||
TimedMetricStats collect(boolean reset);
|
||||
|
||||
/**
|
||||
* Add non empty metrics to the result.
|
||||
* Visit non empty metrics.
|
||||
*/
|
||||
void collect(boolean reset, List<MetaTimedMetric> result);
|
||||
void visit(MetricVisitor visitor);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
import java.util.List;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
/**
|
||||
* A map of timed metrics keyed by a string.
|
||||
@@ -15,7 +13,12 @@ public interface TimedMetricMap {
|
||||
void add(String key, long exeMicros);
|
||||
|
||||
/**
|
||||
* Add non empty metrics to the given result.
|
||||
* Add an execution for the given key including row/bean count.
|
||||
*/
|
||||
void collect(boolean reset, List<MetaTimedMetric> result);
|
||||
void add(String key, long exeMicros, int rows);
|
||||
|
||||
/**
|
||||
* Visit the metric.
|
||||
*/
|
||||
void visit(MetricVisitor visitor);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.NodeUsageCollector;
|
||||
import io.ebean.text.PathProperties;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebean.util.SplitName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ProfileOriginNodeUsage {
|
||||
}
|
||||
|
||||
for (String propName : aggregateUsed) {
|
||||
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
|
||||
BeanProperty beanProp = desc.findPropertyFromPath(propName);
|
||||
if (beanProp == null) {
|
||||
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
@@ -33,6 +34,12 @@ public class CachedBeanDataFromBean {
|
||||
}
|
||||
}
|
||||
|
||||
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
|
||||
if (prop.isElementCollection()) {
|
||||
data.put(prop.getName(), prop.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
|
||||
long version = desc.getVersion(bean);
|
||||
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
|
||||
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
|
||||
|
||||
@@ -21,14 +21,16 @@ public class CachedBeanDataToBean {
|
||||
}
|
||||
|
||||
// load the non-many properties
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (BeanProperty prop : props) {
|
||||
for (BeanProperty prop : desc.propertiesNonMany()) {
|
||||
loadProperty(bean, cacheBeanData, ebi, prop, context);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (BeanPropertyAssocMany<?> aMany : many) {
|
||||
aMany.createReferenceIfNull(bean);
|
||||
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
|
||||
if (prop.isElementCollection()) {
|
||||
loadProperty(bean, cacheBeanData, ebi, prop, context);
|
||||
} else {
|
||||
prop.createReferenceIfNull(bean);
|
||||
}
|
||||
}
|
||||
|
||||
ebi.setLoadedLazy();
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public abstract class AbstractSqlQueryRequest {
|
||||
|
||||
private final SpiSqlBinding query;
|
||||
protected final SpiSqlBinding query;
|
||||
|
||||
protected final SpiEbeanServer server;
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
/**
|
||||
* Not supported JSON or ARRAY expression handler.
|
||||
*/
|
||||
abstract class BaseDbExpression implements DbExpressionHandler {
|
||||
|
||||
private final String concatOperator;
|
||||
|
||||
BaseDbExpression(String concatOperator) {
|
||||
this.concatOperator = concatOperator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.meta.MetaObjectGraphNodeStats;
|
||||
import io.ebean.meta.MetaOrmQueryNode;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
@@ -35,7 +35,7 @@ public class CObjectGraphNodeStatistics {
|
||||
totalBeans.add(beanCount);
|
||||
}
|
||||
|
||||
public MetaObjectGraphNodeStats get(boolean reset) {
|
||||
public MetaOrmQueryNode get(boolean reset) {
|
||||
if (reset) {
|
||||
return new Snapshot(node, startTime.getAndSet(System.currentTimeMillis()), count.sumThenReset(),
|
||||
totalTime.sumThenReset(), totalBeans.sumThenReset());
|
||||
@@ -44,7 +44,7 @@ public class CObjectGraphNodeStatistics {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Snapshot implements MetaObjectGraphNodeStats {
|
||||
private static class Snapshot implements MetaOrmQueryNode {
|
||||
|
||||
private final ObjectGraphNode node;
|
||||
private final long startTime;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.Postgres8Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
|
||||
import io.ebean.migration.util.JdbcClose;
|
||||
import io.ebeaninternal.dbmigration.DbOffline;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -20,6 +23,8 @@ import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
@@ -52,8 +57,7 @@ public class DatabasePlatformFactory {
|
||||
}
|
||||
|
||||
if (serverConfig.getDataSourceConfig().isOffline()) {
|
||||
String m = "You must specify a DatabasePlatformName when you are offline";
|
||||
throw new PersistenceException(m);
|
||||
throw new PersistenceException("You must specify a DatabasePlatformName when you are offline");
|
||||
}
|
||||
// guess using meta data from driver
|
||||
return byDataSource(serverConfig.getDataSource());
|
||||
@@ -84,8 +88,14 @@ public class DatabasePlatformFactory {
|
||||
if (dbName.equals("oracle") || dbName.equals("oracle10") || dbName.equals("oracle9")) {
|
||||
return new OraclePlatform();
|
||||
}
|
||||
if (dbName.equals("sqlserver16")) {
|
||||
return new SqlServer16Platform();
|
||||
}
|
||||
if (dbName.equals("sqlserver17")) {
|
||||
return new SqlServer17Platform();
|
||||
}
|
||||
if (dbName.equals("sqlserver")) {
|
||||
return new SqlServerPlatform();
|
||||
throw new IllegalArgumentException("Please choose the more specific sqlserver16 or sqlserver17 platform. Refer to issue #1340 for details");
|
||||
}
|
||||
if (dbName.equals("sqlanywhere")) {
|
||||
return new SqlAnywherePlatform();
|
||||
@@ -105,20 +115,20 @@ public class DatabasePlatformFactory {
|
||||
*/
|
||||
private DatabasePlatform byDataSource(DataSource dataSource) {
|
||||
|
||||
Connection conn = null;
|
||||
Connection connection = null;
|
||||
try {
|
||||
conn = dataSource.getConnection();
|
||||
DatabaseMetaData metaData = conn.getMetaData();
|
||||
connection = dataSource.getConnection();
|
||||
DatabaseMetaData metaData = connection.getMetaData();
|
||||
|
||||
return byDatabaseMeta(metaData);
|
||||
return byDatabaseMeta(metaData, connection);
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.error(null, ex);
|
||||
@@ -129,7 +139,7 @@ public class DatabasePlatformFactory {
|
||||
/**
|
||||
* Find the platform by the metaData.getDatabaseProductName().
|
||||
*/
|
||||
private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData) throws SQLException {
|
||||
private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData, Connection connection) throws SQLException {
|
||||
|
||||
String dbProductName = metaData.getDatabaseProductName();
|
||||
dbProductName = dbProductName.toLowerCase();
|
||||
@@ -137,7 +147,7 @@ public class DatabasePlatformFactory {
|
||||
if (dbProductName.contains("oracle")) {
|
||||
return new OraclePlatform();
|
||||
} else if (dbProductName.contains("microsoft")) {
|
||||
return new SqlServerPlatform();
|
||||
throw new IllegalArgumentException("For SqlServer please explicitly choose either sqlserver16 or sqlserver17 as the platform via ServerConfig.setDatabasePlatformName. Refer to issue #1340 for more details");
|
||||
} else if (dbProductName.contains("mysql")) {
|
||||
return new MySqlPlatform();
|
||||
} else if (dbProductName.contains("h2")) {
|
||||
@@ -145,7 +155,7 @@ public class DatabasePlatformFactory {
|
||||
} else if (dbProductName.contains("hsql database engine")) {
|
||||
return new HsqldbPlatform();
|
||||
} else if (dbProductName.contains("postgres")) {
|
||||
return new PostgresPlatform();
|
||||
return readPostgres(connection);
|
||||
} else if (dbProductName.contains("sqlite")) {
|
||||
return new SQLitePlatform();
|
||||
} else if (dbProductName.contains("db2")) {
|
||||
@@ -157,4 +167,32 @@ public class DatabasePlatformFactory {
|
||||
// use the standard one
|
||||
return new DatabasePlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a select version() query as it could be Postgres or CockroachDB.
|
||||
*/
|
||||
private static PostgresPlatform readPostgres(Connection connection) {
|
||||
// Postgres driver uses a hardcoded product name so use version() query
|
||||
PreparedStatement statement = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
statement = connection.prepareStatement("SELECT version() AS \"version\"");
|
||||
resultSet = statement.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
String productVersion = resultSet.getString("version").toLowerCase();
|
||||
if (productVersion.contains("cockroach")) {
|
||||
return new CockroachPlatform();
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.warn("Error running detection query on Postgres", e);
|
||||
|
||||
} finally {
|
||||
JdbcClose.close(resultSet);
|
||||
JdbcClose.close(statement);
|
||||
}
|
||||
|
||||
// Real Postgres
|
||||
return new PostgresPlatform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
ServerConfig config = new ServerConfig();
|
||||
config.setName(name);
|
||||
config.loadFromProperties(PropertiesLoader.load());
|
||||
config.loadFromProperties();
|
||||
|
||||
return createServer(config);
|
||||
}
|
||||
@@ -267,7 +267,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
config.setDatabasePlatform(platform);
|
||||
}
|
||||
logger.info("DatabasePlatform name:{} platform:{}", config.getName(), platform.getName());
|
||||
platform.configure(config);
|
||||
platform.configure(config.getPlatformConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.meta.AbstractMetricVisitor;
|
||||
import io.ebean.meta.BasicMetricVisitor;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaObjectGraphNodeStats;
|
||||
import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import io.ebean.meta.MetaOrmQueryNode;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetaQueryPlanStatistic;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,29 +24,26 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
|
||||
return server.collectTransactionStatistics(reset);
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
server.visitMetrics(visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
|
||||
return server.collectQueryStatistics(reset);
|
||||
public BasicMetricVisitor visitBasic() {
|
||||
BasicMetricVisitor basic = new BasicMetricVisitor();
|
||||
visitMetrics(basic);
|
||||
return basic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
|
||||
|
||||
CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset);
|
||||
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
|
||||
desc.collectQueryPlanStatistics(collector);
|
||||
}
|
||||
return collector.getList();
|
||||
public void resetAllMetrics() {
|
||||
server.visitMetrics(new ResetVisitor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
|
||||
public List<MetaOrmQueryNode> collectNodeStatistics(boolean reset) {
|
||||
|
||||
List<MetaObjectGraphNodeStats> list = new ArrayList<>();
|
||||
List<MetaOrmQueryNode> list = new ArrayList<>();
|
||||
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
|
||||
if (!nodeStatistics.isEmpty()) {
|
||||
list.add(nodeStatistics.get(reset));
|
||||
@@ -54,4 +52,29 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor that resets the statistics but doesn't collect them.
|
||||
*/
|
||||
private static class ResetVisitor extends AbstractMetricVisitor {
|
||||
|
||||
ResetVisitor() {
|
||||
super(true, true, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTimed(MetaTimedMetric metric) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitQuery(MetaQueryMetric metric) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOrmQuery(MetaOrmQueryMetric metric) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.ebean.Filter;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
import io.ebean.FutureRowCount;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.PagedList;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
@@ -48,8 +49,7 @@ import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.Property;
|
||||
@@ -802,7 +802,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void commitTransaction() {
|
||||
transactionManager.scope().commit();
|
||||
currentTransaction().commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -810,7 +810,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void rollbackTransaction() {
|
||||
transactionManager.scope().rollback();
|
||||
currentTransaction().rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -843,7 +843,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void endTransaction() {
|
||||
transactionManager.scope().end();
|
||||
Transaction transaction = currentTransaction();
|
||||
if (transaction != null) {
|
||||
transaction.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -916,6 +919,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new DefaultUpdateQuery<>(createQuery(beanType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean, MergeOptions options) {
|
||||
merge(bean, options, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean, MergeOptions options, Transaction transaction) {
|
||||
BeanDescriptor<?> desc = getBeanDescriptor(bean.getClass());
|
||||
if (desc == null) {
|
||||
throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?");
|
||||
}
|
||||
executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Query<T> find(Class<T> beanType) {
|
||||
return createQuery(beanType);
|
||||
@@ -952,7 +969,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Query<T> createQuery(Class<T> beanType, String eql) {
|
||||
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType, String eql) {
|
||||
DefaultOrmQuery<T> query = createQuery(beanType);
|
||||
EqlParser.parse(eql, query);
|
||||
return query;
|
||||
@@ -985,6 +1002,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
|
||||
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
|
||||
String sql = descriptor.getNamedRawSql(namedQuery);
|
||||
if (sql == null) {
|
||||
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + dtoType.getName());
|
||||
}
|
||||
return new DefaultDtoQuery<>(this, descriptor, sql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
|
||||
|
||||
@@ -1272,6 +1299,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Class<?> beanType, Object beanId, Transaction transaction) {
|
||||
List<Object> ids = findIds(find(beanType).setId(beanId), transaction);
|
||||
return !ids.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
|
||||
|
||||
@@ -2192,10 +2225,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
|
||||
return transactionManager.collectTransactionStatistics(reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Property> checkUniqueness(Object bean) {
|
||||
return checkUniqueness(bean, null);
|
||||
@@ -2237,11 +2266,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
|
||||
/**
|
||||
* Returns a set of properties if saving the bean will violate the unique constraints
|
||||
* (definded by given properties).
|
||||
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
|
||||
*/
|
||||
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
|
||||
Transaction transaction) {
|
||||
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props, Transaction transaction) {
|
||||
BeanProperty idProperty = beanDesc.getIdProperty();
|
||||
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
|
||||
ExpressionList<?> exprList = query.where();
|
||||
@@ -2261,15 +2288,24 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
if (findCount(query, transaction) > 0) {
|
||||
Set<Property> ret = new LinkedHashSet<>();
|
||||
for (Property prop : props) {
|
||||
ret.add(prop);
|
||||
}
|
||||
Collections.addAll(ret, props);
|
||||
return ret;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
|
||||
return dtoBeanManager.collectStats(reset);
|
||||
@Override
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
visitor.visitStart();
|
||||
if (visitor.isCollectTransactionMetrics()) {
|
||||
transactionManager.visitMetrics(visitor);
|
||||
}
|
||||
if (visitor.isCollectQueryMetrics()) {
|
||||
beanDescriptorManager.visitMetrics(visitor);
|
||||
dtoBeanManager.visitMetrics(visitor);
|
||||
relationalQueryEngine.visitMetrics(visitor);
|
||||
persister.visitMetrics(visitor);
|
||||
}
|
||||
visitor.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
this(null, sql, new BindParams());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
addPos = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGetKey() {
|
||||
execute();
|
||||
@@ -182,8 +187,9 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addParameter(Object value) {
|
||||
public SqlUpdate setNextParameter(Object value) {
|
||||
setParameter(++addPos, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.dto.DtoNamedQueries;
|
||||
import io.ebeaninternal.xmlmapping.XmlMappingReader;
|
||||
import io.ebeaninternal.xmlmapping.model.XmDto;
|
||||
import io.ebeaninternal.xmlmapping.model.XmEbean;
|
||||
import io.ebeaninternal.xmlmapping.model.XmRawSql;
|
||||
import org.avaje.classpath.scanner.ClassPathScanner;
|
||||
import org.avaje.classpath.scanner.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Reads the Xml deployment information.
|
||||
*/
|
||||
class InternalConfigXmlRead {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InternalConfigXmlRead.class);
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
private final Map<Class<?>, DtoNamedQueries> dtoNamedQueries = new HashMap<>();
|
||||
|
||||
private List<XmEbean> xmlEbeanList;
|
||||
|
||||
InternalConfigXmlRead(ServerConfig serverConfig) {
|
||||
this.serverConfig = serverConfig;
|
||||
this.classLoader = serverConfig.getClassLoadConfig().getClassLoader();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
this.xmlEbeanList = XmlMappingReader.readByResourceName(classLoader, "ebean.xml");
|
||||
xmlEbeanList.addAll(XmlMappingReader.readByResourceList(xmlMappingResources()));
|
||||
}
|
||||
|
||||
private List<Resource> xmlMappingResources() {
|
||||
List<ClassPathScanner> scanners = ClassPathScanners.find(serverConfig);
|
||||
List<String> mappingLocations = serverConfig.getMappingLocations();
|
||||
List<Resource> resourceList = new ArrayList<>();
|
||||
|
||||
long st = System.currentTimeMillis();
|
||||
if (mappingLocations != null && !mappingLocations.isEmpty()) {
|
||||
for (ClassPathScanner finder : scanners) {
|
||||
for (String mappingLocation : mappingLocations) {
|
||||
resourceList.addAll(finder.scanForResources(mappingLocation, resourceName -> resourceName.endsWith(".xml")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long searchTime = System.currentTimeMillis() - st;
|
||||
log.debug("Classpath search mappings[{}] searchTime[{}]", resourceList.size(), searchTime);
|
||||
return resourceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the XML deployment information for entity beans.
|
||||
*/
|
||||
List<XmEbean> xmlDeployment() {
|
||||
return xmlEbeanList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
return dtoNamedQueries;
|
||||
}
|
||||
|
||||
private void readDtoMapping(XmDto dto) {
|
||||
|
||||
String dtoClassName = dto.getClazz();
|
||||
Class<?> dtoClass;
|
||||
try {
|
||||
dtoClass = Class.forName(dtoClassName, false, classLoader);
|
||||
} catch (Exception e) {
|
||||
log.error("Could not load dto bean class " + dtoClassName + " for ebean xml entry");
|
||||
return;
|
||||
}
|
||||
|
||||
DtoNamedQueries namedQueries = dtoNamedQueries.computeIfAbsent(dtoClass, aClass -> new DtoNamedQueries());
|
||||
|
||||
for (XmRawSql sql : dto.getRawSql()) {
|
||||
namedQueries.addRawSql(sql.getName(), sql.getQuery().getValue());
|
||||
}
|
||||
|
||||
if (!dto.getNamedQuery().isEmpty()) {
|
||||
log.error("Only raw-sql named queries supported for DTO beans - bean:" + dtoClass);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,6 +42,8 @@ import io.ebeaninternal.server.deploy.parse.DeployInherit;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployUtil;
|
||||
import io.ebeaninternal.server.dto.DtoBeanManager;
|
||||
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
|
||||
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
|
||||
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
|
||||
import io.ebeaninternal.server.persist.Binder;
|
||||
import io.ebeaninternal.server.persist.DefaultPersister;
|
||||
import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
@@ -153,10 +155,12 @@ public class InternalConfiguration {
|
||||
|
||||
this.deployCreateProperties = new DeployCreateProperties(typeManager);
|
||||
this.deployUtil = new DeployUtil(typeManager, serverConfig);
|
||||
this.dtoBeanManager = new DtoBeanManager(typeManager);
|
||||
|
||||
InternalConfigXmlRead xmlRead = new InternalConfigXmlRead(serverConfig);
|
||||
|
||||
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlRead.readDtoMapping());
|
||||
this.beanDescriptorManager = new BeanDescriptorManager(this);
|
||||
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
|
||||
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlRead.xmlDeployment());
|
||||
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
|
||||
beanDescriptorManager.scheduleBackgroundTrim();
|
||||
|
||||
@@ -267,18 +271,7 @@ public class InternalConfiguration {
|
||||
* Return the JSON expression handler for the given database platform.
|
||||
*/
|
||||
private DbExpressionHandler getDbExpressionHandler(DatabasePlatform databasePlatform) {
|
||||
Platform platform = databasePlatform.getPlatform();
|
||||
String concatOperator = databasePlatform.getConcatOperator();
|
||||
switch (platform) {
|
||||
case POSTGRES:
|
||||
return new PostgresDbExpression(concatOperator);
|
||||
case ORACLE:
|
||||
return new OracleDbExpression(concatOperator);
|
||||
case SQLSERVER:
|
||||
return new SqlServerDbExpression(concatOperator);
|
||||
default:
|
||||
return new BasicDbExpression(concatOperator);
|
||||
}
|
||||
return DbExpressionHandlerFactory.from(databasePlatform);
|
||||
}
|
||||
|
||||
private MultiValueBind createMultiValueBind(Platform platform) {
|
||||
@@ -304,7 +297,7 @@ public class InternalConfiguration {
|
||||
}
|
||||
|
||||
public RelationalQueryEngine createRelationalQueryEngine() {
|
||||
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getDbTypeConfig().getDbUuid().useBinaryOptimized());
|
||||
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getPlatformConfig().getDbUuid().useBinaryOptimized());
|
||||
}
|
||||
|
||||
public OrmQueryEngine createOrmQueryEngine() {
|
||||
|
||||
@@ -79,6 +79,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
|
||||
private List<T> cacheBeans;
|
||||
|
||||
private BeanPropertyAssocMany<?> manyProperty;
|
||||
|
||||
/**
|
||||
* Create the InternalQueryRequest.
|
||||
*/
|
||||
@@ -496,11 +498,18 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many property that is fetched in the query or null if there is
|
||||
* not one.
|
||||
* Determine and return the ToMany property that is included in the query.
|
||||
*/
|
||||
public BeanPropertyAssocMany<?> determineMany() {
|
||||
manyProperty = beanDescriptor.getManyProperty(query);
|
||||
return manyProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many property that is fetched in the query or null if there is not one.
|
||||
*/
|
||||
public BeanPropertyAssocMany<?> getManyProperty() {
|
||||
return beanDescriptor.getManyProperty(query);
|
||||
return manyProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
|
||||
UPDATESQL(EVT_UPDATESQL),
|
||||
CALLABLESQL(EVT_CALLABLESQL);
|
||||
String profileEventId;
|
||||
|
||||
Type(String profileEventId) {
|
||||
this.profileEventId = profileEventId;
|
||||
}
|
||||
@@ -36,12 +37,30 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
|
||||
|
||||
final PersistExecute persistExecute;
|
||||
|
||||
protected String label;
|
||||
|
||||
protected long startNanos;
|
||||
|
||||
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
|
||||
super(server, t);
|
||||
this.persistExecute = persistExecute;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by CallableSqlRequest and UpdateSqlRequest.
|
||||
*/
|
||||
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
|
||||
super(server, t);
|
||||
this.persistExecute = persistExecute;
|
||||
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute, String label) {
|
||||
this(server, t, persistExecute);
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectively set start nanos if we are collecting metrics on a label.
|
||||
*/
|
||||
public void startBind(boolean batchThisRequest) {
|
||||
if (!batchThisRequest && label != null) {
|
||||
startNanos = System.nanoTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeaninternal.server.persist.BatchedSqlException;
|
||||
import io.ebeaninternal.server.persist.Flags;
|
||||
import io.ebeaninternal.server.persist.PersistExecute;
|
||||
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdate;
|
||||
@@ -34,6 +35,7 @@ import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -75,6 +77,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
private final boolean publish;
|
||||
|
||||
private int flags;
|
||||
|
||||
private DocStoreMode docStoreMode;
|
||||
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
@@ -104,6 +108,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean updatedManysOnly;
|
||||
|
||||
/**
|
||||
* Element collection change as part of bean cache.
|
||||
*/
|
||||
private Map<String, Object> collectionChanges;
|
||||
|
||||
/**
|
||||
* Many properties that were cascade saved (and hence might need caches updated later).
|
||||
*/
|
||||
@@ -151,8 +160,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean getterCallback;
|
||||
|
||||
/**
|
||||
* postUpdate notifications. Used to combine bean and element update updates into single postUpdate event.
|
||||
*/
|
||||
private int pendingPostUpdateNotify;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean) bean;
|
||||
@@ -165,7 +179,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
this.type = type;
|
||||
this.docStoreMode = calcDocStoreMode(transaction, type);
|
||||
if (saveRecurse) {
|
||||
this.flags = flags;
|
||||
if (Flags.isRecurse(flags)) {
|
||||
this.persistCascade = t.isPersistCascade();
|
||||
}
|
||||
|
||||
@@ -179,7 +194,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanDescriptor.checkMutableProperties(intercept);
|
||||
}
|
||||
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
|
||||
this.publish = publish;
|
||||
this.publish = Flags.isPublish(flags);
|
||||
if (isMarkDraftDirty(publish)) {
|
||||
beanDescriptor.setDraftDirty(entityBean, true);
|
||||
}
|
||||
@@ -231,6 +246,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
if (createImplicitTransIfRequired()) {
|
||||
docStoreMode = calcDocStoreMode(transaction, type);
|
||||
}
|
||||
checkBatchEscalationOnCascade();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for batch escalation on cascade.
|
||||
*/
|
||||
public void checkBatchEscalationOnCascade() {
|
||||
if (transaction.checkBatchEscalationOnCascade(this)) {
|
||||
// we escalated to use batch mode so flush when done
|
||||
// but if createdTransaction then commit will flush it
|
||||
@@ -326,11 +348,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
@Override
|
||||
public void preGetterTrigger(int propertyIndex) {
|
||||
if (propertyIndex < 0 || beanDescriptor.isGeneratedProperty(propertyIndex)) {
|
||||
if (flushBatchOnGetter(propertyIndex)) {
|
||||
transaction.flushBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean flushBatchOnGetter(int propertyIndex) {
|
||||
// propertyIndex of -1 the Id property, no flush for get Id on UPDATE
|
||||
return propertyIndex == -1 ? type == Type.INSERT : beanDescriptor.isGeneratedProperty(propertyIndex);
|
||||
}
|
||||
|
||||
public void setSkipBatchForTopLevel() {
|
||||
skipBatchForTopLevel = true;
|
||||
}
|
||||
@@ -847,6 +874,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
private void postUpdateNotify() {
|
||||
if (pendingPostUpdateNotify > 0) {
|
||||
// invoke the delayed postUpdate notification (combined with element collection update)
|
||||
controller.postUpdate(this);
|
||||
} else {
|
||||
// batched update with no element collection, send postUpdate notification once it executes
|
||||
pendingPostUpdateNotify = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggressive L1 and L2 cache cleanup for deletes.
|
||||
*/
|
||||
@@ -894,13 +931,42 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the preUpdate event fires (for case where only element collection has changed).
|
||||
*/
|
||||
public void preElementCollectionUpdate() {
|
||||
if (controller != null && !dirty) {
|
||||
// fire preUpdate notification when only element collection updated
|
||||
controller.preUpdate(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine with the beans postUpdate event notification.
|
||||
*/
|
||||
public boolean postElementCollectionUpdate() {
|
||||
if (controller != null) {
|
||||
pendingPostUpdateNotify += 2;
|
||||
}
|
||||
if (!dirty) {
|
||||
setNotifyCache();
|
||||
}
|
||||
return notifyCache;
|
||||
}
|
||||
|
||||
private void controllerPost() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
controller.postInsert(this);
|
||||
break;
|
||||
case UPDATE:
|
||||
controller.postUpdate(this);
|
||||
if (pendingPostUpdateNotify == -1) {
|
||||
// notify now - batched bean update with no element collection
|
||||
controller.postUpdate(this);
|
||||
} else {
|
||||
// delay notify to combine with element collection update
|
||||
pendingPostUpdateNotify++;
|
||||
}
|
||||
break;
|
||||
case SOFT_DELETE:
|
||||
controller.postSoftDelete(this);
|
||||
@@ -1020,13 +1086,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
setNotifyCache();
|
||||
addEvent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if only many properties where updated.
|
||||
*/
|
||||
public boolean isUpdatedManysOnly() {
|
||||
return updatedManysOnly;
|
||||
postUpdateNotify();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1076,6 +1136,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return requestUpdateAllLoadedProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the flags set on this persist request.
|
||||
*/
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is a 'publish' action.
|
||||
*/
|
||||
@@ -1226,4 +1293,71 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
public void profile() {
|
||||
profileBase(type.profileEventId, profileOffset, beanDescriptor.getProfileId(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request flags indicating this is an insert.
|
||||
*/
|
||||
public void flagInsert() {
|
||||
flags = Flags.setInsert(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the request insert flag indicating this is an update.
|
||||
*/
|
||||
public void flagUpdate() {
|
||||
flags = Flags.unsetInsert(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is an insert.
|
||||
*/
|
||||
public boolean isInsertedParent() {
|
||||
return Flags.isInsert(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element collection change to L2 bean cache update.
|
||||
*/
|
||||
public void addCollectionChange(String name, Object value) {
|
||||
if (collectionChanges == null) {
|
||||
collectionChanges = new LinkedHashMap<>();
|
||||
}
|
||||
collectionChanges.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the bean update for the L2 cache.
|
||||
*/
|
||||
public void addBeanUpdate(CacheChangeSet changeSet) {
|
||||
|
||||
if (!updatedManysOnly || collectionChanges != null) {
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
EntityBean bean = getEntityBean();
|
||||
boolean[] dirtyProperties = getDirtyProperties();
|
||||
if (dirtyProperties != null) {
|
||||
for (int i = 0; i < dirtyProperties.length; i++) {
|
||||
if (dirtyProperties[i]) {
|
||||
BeanProperty property = beanDescriptor.propertyByIndex(i);
|
||||
if (property.isCacheDataInclude()) {
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(beanDescriptor, idValue, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collectionChanges != null) {
|
||||
// add element collection update
|
||||
changes.putAll(collectionChanges);
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
public PersistRequestCallableSql(SpiEbeanServer server,
|
||||
CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
|
||||
public PersistRequestCallableSql(SpiEbeanServer server, CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
super(server, t, persistExecute, cs.getLabel());
|
||||
this.type = PersistRequest.Type.CALLABLESQL;
|
||||
this.callableSql = (SpiCallableSql) cs;
|
||||
}
|
||||
@@ -88,7 +87,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
|
||||
*/
|
||||
@Override
|
||||
public void postExecute() {
|
||||
|
||||
if (startNanos > 0) {
|
||||
persistExecute.collectSqlCall(label, startNanos, rowCount);
|
||||
}
|
||||
if (transaction.isLogSummary()) {
|
||||
String m = "CallableSql label[" + callableSql.getLabel() + "]" + " rows[" + rowCount + "]" + " bind[" + bindLog + "]";
|
||||
transaction.logSummary(m);
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
|
||||
public PersistRequestOrmUpdate(SpiEbeanServer server, BeanManager<?> mgr, SpiUpdate<?> ormUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
super(server, t, persistExecute, ormUpdate.getLabel());
|
||||
this.beanDescriptor = mgr.getBeanDescriptor();
|
||||
this.ormUpdate = ormUpdate;
|
||||
}
|
||||
@@ -86,7 +86,9 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
|
||||
*/
|
||||
@Override
|
||||
public void postExecute() {
|
||||
|
||||
if (startNanos > 0) {
|
||||
persistExecute.collectOrmUpdate(label, startNanos, rowCount);
|
||||
}
|
||||
OrmUpdateType ormUpdateType = ormUpdate.getOrmUpdateType();
|
||||
String tableName = ormUpdate.getBaseTable();
|
||||
|
||||
|
||||
@@ -30,12 +30,13 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate updateSql,
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
super(server, t, persistExecute, sqlUpdate.getLabel());
|
||||
this.type = Type.UPDATESQL;
|
||||
this.updateSql = (SpiSqlUpdate) updateSql;
|
||||
this.updateSql = (SpiSqlUpdate) sqlUpdate;
|
||||
updateSql.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +103,9 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
*/
|
||||
@Override
|
||||
public void postExecute() {
|
||||
|
||||
if (startNanos > 0) {
|
||||
persistExecute.collectSqlUpdate(label, startNanos, rowCount);
|
||||
}
|
||||
if (transaction.isLogSummary()) {
|
||||
String m = description + " table[" + tableName + "] rows[" + rowCount + "] bind[" + bindLog + "]";
|
||||
transaction.logSummary(m);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.CallableSql;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.Update;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -15,6 +19,11 @@ import java.util.List;
|
||||
*/
|
||||
public interface Persister {
|
||||
|
||||
/**
|
||||
* Merge the bean.
|
||||
*/
|
||||
int merge(BeanDescriptor<?> desc, EntityBean entityBean, MergeOptions options, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Update the bean.
|
||||
*/
|
||||
@@ -78,4 +87,8 @@ public interface Persister {
|
||||
*/
|
||||
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Visit the metrics.
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
|
||||
@@ -38,10 +39,14 @@ public class PlatformDdlBuilder {
|
||||
return new PlatformDdl(platform);
|
||||
case POSTGRES:
|
||||
return new PostgresDdl(platform);
|
||||
case SQLSERVER:
|
||||
return new SqlServerDdl(platform);
|
||||
case COCKROACH:
|
||||
return new CockroachDdl(platform);
|
||||
case SQLANYWHERE:
|
||||
return new PlatformDdl(platform);
|
||||
case SQLSERVER16:
|
||||
case SQLSERVER17:
|
||||
case SQLSERVER:
|
||||
return new SqlServerDdl(platform);
|
||||
default:
|
||||
return new PlatformDdl(platform);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
@@ -29,4 +30,13 @@ public interface RelationalQueryEngine {
|
||||
*/
|
||||
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
|
||||
|
||||
/**
|
||||
* Collect SQL query execution statistics.
|
||||
*/
|
||||
void collect(String label, long exeMicros, int rows);
|
||||
|
||||
/**
|
||||
* Visit the metrics.
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,11 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
|
||||
@Override
|
||||
protected void requestComplete() {
|
||||
|
||||
String label = query.getLabel();
|
||||
if (label != null) {
|
||||
long exeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryEngine.collect(label, exeMicros, rows);
|
||||
}
|
||||
}
|
||||
|
||||
public void findEach(Consumer<SqlRow> consumer) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
|
||||
|
||||
final BeanPropertyAssocMany<T> many;
|
||||
final BeanDescriptor<T> targetDescriptor;
|
||||
final String propertyName;
|
||||
|
||||
BeanCollectionLoader loader;
|
||||
|
||||
BaseCollectionHelp(BeanPropertyAssocMany<T> many) {
|
||||
this.many = many;
|
||||
this.targetDescriptor = many.getTargetDescriptor();
|
||||
this.propertyName = many.getName();
|
||||
}
|
||||
|
||||
BaseCollectionHelp() {
|
||||
this.many = null;
|
||||
this.targetDescriptor = null;
|
||||
this.propertyName = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoader(BeanCollectionLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
|
||||
if (withCheck) {
|
||||
collection.internalAddWithCheck(bean);
|
||||
} else {
|
||||
collection.internalAdd(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection underlying(Object value) {
|
||||
if (value instanceof BeanCollection) {
|
||||
return ((BeanCollection)value).getActualDetails();
|
||||
} else {
|
||||
return (Collection)value;
|
||||
}
|
||||
}
|
||||
|
||||
void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection<?> list) throws IOException {
|
||||
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
|
||||
ctx.beginAssocMany(name);
|
||||
for (Object bean : list) {
|
||||
jsonWriteElement(ctx, bean);
|
||||
}
|
||||
ctx.endAssocMany();
|
||||
}
|
||||
}
|
||||
|
||||
void jsonWriteElement(SpiJsonWriter ctx, Object bean) throws IOException {
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.bean.BeanDiffVisitor;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.util.ArrayStack;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -82,10 +82,10 @@ class BeanChangeJson implements BeanDiffVisitor {
|
||||
void flush() {
|
||||
try {
|
||||
newJson.writeEndObject();
|
||||
newJson.gen().flush();
|
||||
newJson.flush();
|
||||
if (oldJson != null) {
|
||||
oldJson.writeEndObject();
|
||||
oldJson.gen().flush();
|
||||
oldJson.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
|
||||
@@ -7,20 +7,27 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.query.CQueryCollectionAdd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Helper functions for performing tasks on Lists Sets or Maps.
|
||||
*/
|
||||
public interface BeanCollectionHelp<T> {
|
||||
public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
|
||||
|
||||
/**
|
||||
* Set the EbeanServer that owns the configuration.
|
||||
*/
|
||||
void setLoader(BeanCollectionLoader loader);
|
||||
|
||||
/**
|
||||
* Return the underlying collection of beans.
|
||||
*/
|
||||
Collection underlying(Object value);
|
||||
|
||||
/**
|
||||
* Return the mechanism to add beans to the underlying collection.
|
||||
* <p>
|
||||
|
||||
@@ -18,16 +18,17 @@ public class BeanCollectionHelpFactory {
|
||||
/**
|
||||
* Create the helper based on the many property.
|
||||
*/
|
||||
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> manyProperty) {
|
||||
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> many) {
|
||||
|
||||
ManyType manyType = manyProperty.getManyType();
|
||||
boolean elementCollection = many.isElementCollection();
|
||||
ManyType manyType = many.getManyType();
|
||||
switch (manyType) {
|
||||
case LIST:
|
||||
return new BeanListHelp<>(manyProperty);
|
||||
return elementCollection ? new BeanListHelpElement<>(many) : new BeanListHelp<>(many);
|
||||
case SET:
|
||||
return new BeanSetHelp<>(manyProperty);
|
||||
return elementCollection ? new BeanSetHelpElement<>(many) : new BeanSetHelp<>(many);
|
||||
case MAP:
|
||||
return new BeanMapHelp<>(manyProperty);
|
||||
return elementCollection ? new BeanMapHelpElement<>(many) :new BeanMapHelp<>(many);
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + manyType);
|
||||
}
|
||||
@@ -35,9 +36,7 @@ public class BeanCollectionHelpFactory {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> BeanCollectionHelp<T> create(OrmQueryRequest<T> request) {
|
||||
|
||||
SpiQuery.Type manyType = request.getQuery().getType();
|
||||
public static <T> BeanCollectionHelp<T> create(SpiQuery.Type manyType, OrmQueryRequest<T> request) {
|
||||
|
||||
if (manyType == SpiQuery.Type.LIST) {
|
||||
return LIST_HELP;
|
||||
|
||||
@@ -11,6 +11,16 @@ import java.util.Map;
|
||||
*/
|
||||
public class BeanCollectionUtil {
|
||||
|
||||
/**
|
||||
* Return true if this is a bean collection and not considered dirty.
|
||||
*/
|
||||
public static boolean isModified(Object collection) {
|
||||
if ((collection instanceof BeanCollection<?>)) {
|
||||
return ((BeanCollection<?>) collection).holdsModifications();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the details of the collection or map taking care to avoid
|
||||
* unnecessary fetching of the data.
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.ebean.event.changelog.ChangeType;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.plugin.BeanDocType;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
@@ -40,6 +41,8 @@ import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiUpdatePlan;
|
||||
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
import io.ebeaninternal.server.cache.CachedManyIds;
|
||||
@@ -61,12 +64,15 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.DmlUtil;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
|
||||
import io.ebeaninternal.server.query.SqlTreeProperty;
|
||||
import io.ebeaninternal.server.query.ExtraJoin;
|
||||
import io.ebeaninternal.server.query.STreeProperty;
|
||||
import io.ebeaninternal.server.query.STreePropertyAssoc;
|
||||
import io.ebeaninternal.server.query.STreePropertyAssocMany;
|
||||
import io.ebeaninternal.server.query.STreePropertyAssocOne;
|
||||
import io.ebeaninternal.server.query.STreeType;
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.util.SortByClause;
|
||||
@@ -100,7 +106,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* Describes Beans including their deployment information.
|
||||
*/
|
||||
public class BeanDescriptor<T> implements BeanType<T> {
|
||||
public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
|
||||
|
||||
@@ -114,7 +120,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, SqlTreeProperty> dynamicProperty = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, SpiRawSql> namedRawSql;
|
||||
|
||||
@@ -229,7 +235,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
/**
|
||||
* The type of bean this describes.
|
||||
*/
|
||||
private final Class<T> beanType;
|
||||
final Class<T> beanType;
|
||||
|
||||
protected final Class<?> rootBeanType;
|
||||
|
||||
@@ -239,7 +245,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
private final BeanDescriptorMap owner;
|
||||
|
||||
|
||||
private final String[] properties;
|
||||
final String[] properties;
|
||||
|
||||
/**
|
||||
* Intercept pre post on insert,update, and delete .
|
||||
@@ -400,7 +406,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
private final BeanDescriptorDraftHelp<T> draftHelp;
|
||||
private final BeanDescriptorCacheHelp<T> cacheHelp;
|
||||
private final BeanDescriptorJsonHelp<T> jsonHelp;
|
||||
final BeanDescriptorJsonHelp<T> jsonHelp;
|
||||
private DocStoreBeanAdapter<T> docStoreAdapter;
|
||||
private DocumentMapping docMapping;
|
||||
private boolean docStoreEmbeddedInvalidation;
|
||||
@@ -422,8 +428,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
this.name = InternString.intern(deploy.getName());
|
||||
this.baseTableAlias = "t0";
|
||||
this.fullName = InternString.intern(deploy.getFullName());
|
||||
this.locationById = ProfileLocation.createAt(fullName+".byId");
|
||||
this.locationAll = ProfileLocation.createAt(fullName+".all");
|
||||
this.locationById = ProfileLocation.createAt(fullName + ".byId");
|
||||
this.locationAll = ProfileLocation.createAt(fullName + ".all");
|
||||
this.profileBeanId = deploy.getProfileId();
|
||||
this.beanType = deploy.getBeanType();
|
||||
this.rootBeanType = PersistenceContextUtil.root(beanType);
|
||||
@@ -601,7 +607,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
/**
|
||||
* Create an entity bean that is used as a prototype/factory to create new instances.
|
||||
*/
|
||||
private EntityBean createPrototypeEntityBean(Class<T> beanType) {
|
||||
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
|
||||
if (Modifier.isAbstract(beanType.getModifiers())) {
|
||||
return null;
|
||||
}
|
||||
@@ -656,27 +662,29 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public BeanProperty propertyByIndex(int pos) {
|
||||
return propertiesIndex[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the Id properties first.
|
||||
* <p>
|
||||
* These properties need to be initialised prior to the association properties
|
||||
* as they are used to get the imported and exported properties.
|
||||
* </p>
|
||||
*
|
||||
* @param withHistoryTables map populated if @History is supported on this entity bean
|
||||
*/
|
||||
public void initialiseId(Map<String, String> withHistoryTables, Map<String, String> draftTables) {
|
||||
public void initialiseId(BeanDescriptorInitContext initContext) {
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("BeanDescriptor initialise " + fullName);
|
||||
}
|
||||
|
||||
if (draftable) {
|
||||
draftTables.put(baseTable, draftTable);
|
||||
initContext.addDraft(baseTable, draftTable);
|
||||
}
|
||||
if (historySupport) {
|
||||
// add mapping (used to swap out baseTable for asOf queries)
|
||||
withHistoryTables.put(baseTable, baseTableAsOf);
|
||||
initContext.addHistory(baseTable, baseTableAsOf);
|
||||
}
|
||||
|
||||
if (inheritInfo != null) {
|
||||
@@ -686,28 +694,24 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
if (isEmbedded()) {
|
||||
// initialise all the properties
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
prop.initialise();
|
||||
prop.initialise(initContext);
|
||||
}
|
||||
} else {
|
||||
// initialise just the Id properties
|
||||
if (idProperty != null) {
|
||||
idProperty.initialise();
|
||||
idProperty.initialise(initContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the exported and imported parts for associated properties.
|
||||
*
|
||||
* @param asOfTableMap the map of base tables to associated 'with history' tables
|
||||
* @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name
|
||||
* @param draftTableMap the map of base tables to associated 'draft' tables.
|
||||
*/
|
||||
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix, Map<String, String> draftTableMap) {
|
||||
public void initialiseOther(BeanDescriptorInitContext initContext) {
|
||||
|
||||
for (BeanPropertyAssocMany<?> aPropertiesManyToMany1 : propertiesManyToMany) {
|
||||
for (BeanPropertyAssocMany<?> many : propertiesManyToMany) {
|
||||
// register associated draft table for M2M intersection
|
||||
aPropertiesManyToMany1.registerDraftIntersectionTable(draftTableMap);
|
||||
many.registerDraftIntersectionTable(initContext);
|
||||
}
|
||||
|
||||
if (historySupport) {
|
||||
@@ -717,8 +721,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
// register associated history table for M2M intersection
|
||||
if (!aPropertiesManyToMany.isExcludedFromHistory()) {
|
||||
TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin();
|
||||
String intersectionTableName = intersectionTableJoin.getTable();
|
||||
asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
|
||||
initContext.addHistoryIntersection(intersectionTableJoin.getTable());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,14 +730,14 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
// initialise all the non-id properties
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
if (!prop.isId()) {
|
||||
prop.initialise();
|
||||
prop.initialise(initContext);
|
||||
}
|
||||
prop.registerColumn(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (unidirectional != null) {
|
||||
unidirectional.initialise();
|
||||
unidirectional.initialise(initContext);
|
||||
}
|
||||
|
||||
idBinder.initialise();
|
||||
@@ -777,7 +780,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
for (BeanProperty prop : propertiesNonTransient) {
|
||||
if (prop.isUnique()) {
|
||||
propertiesUnique.add(new BeanProperty[] { prop });
|
||||
propertiesUnique.add(new BeanProperty[]{prop});
|
||||
}
|
||||
}
|
||||
// convert unique columns to properties
|
||||
@@ -799,7 +802,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
if (propName == null) {
|
||||
return;
|
||||
}
|
||||
props[i] = findBeanProperty(propName);
|
||||
props[i] = findProperty(propName);
|
||||
}
|
||||
if (props.length == 1) {
|
||||
for (BeanProperty[] inserted : propertiesUnique) {
|
||||
@@ -861,6 +864,16 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind all the property values to the SqlUpdate.
|
||||
*/
|
||||
public void bindElementValue(SqlUpdate insert, Object value) {
|
||||
EntityBean bean = (EntityBean) value;
|
||||
for (BeanProperty property : propertiesBaseScalar) {
|
||||
insert.setNextParameter(property.getValue(bean));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ReadAuditLogger for logging read audit events.
|
||||
*/
|
||||
@@ -936,7 +949,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
SpiJsonWriter jsonWriter = createJsonWriter(writer);
|
||||
|
||||
jsonWriteForInsert(jsonWriter, request.getEntityBean());
|
||||
jsonWriter.gen().flush();
|
||||
jsonWriter.flush();
|
||||
|
||||
return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null);
|
||||
|
||||
@@ -950,6 +963,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return ebeanServer.jsonExtended().createJsonWriter(writer);
|
||||
}
|
||||
|
||||
SpiJsonReader createJsonReader(String json) {
|
||||
return ebeanServer.jsonExtended().createJsonRead(this, json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the diff for inserts with flattened non-null property values.
|
||||
*/
|
||||
@@ -1016,7 +1033,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
Object[] bindValues = idBinder.getBindValues(id);
|
||||
for (Object bindValue : bindValues) {
|
||||
sqlDelete.addParameter(bindValue);
|
||||
sqlDelete.setNextParameter(bindValue);
|
||||
}
|
||||
|
||||
return sqlDelete;
|
||||
@@ -1421,7 +1438,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cacheBeanPutAll(Collection<?> beans) {
|
||||
if (!beans.isEmpty()) {
|
||||
cacheHelp.beanPutAll((Collection<EntityBean>)beans);
|
||||
cacheHelp.beanPutAll((Collection<EntityBean>) beans);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1598,10 +1615,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return new DeployUpdateParser(this).parse(ormUpdateStatement);
|
||||
}
|
||||
|
||||
public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) {
|
||||
/**
|
||||
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
|
||||
*/
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
if (!queryPlan.isEmptyStats()) {
|
||||
collector.add(queryPlan.getSnapshot(collector.isReset()));
|
||||
visitor.visitOrmQuery(queryPlan.getSnapshot(visitor.isReset()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1636,6 +1656,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
/**
|
||||
* Execute the postLoad if a BeanPostLoad exists for this bean.
|
||||
*/
|
||||
@Override
|
||||
public void postLoad(Object bean) {
|
||||
if (beanPostLoad != null) {
|
||||
beanPostLoad.postLoad(bean);
|
||||
@@ -1784,6 +1805,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return idBinder instanceof IdBinderSimple;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasId() {
|
||||
return idProperty != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this type has a simple Id and the platform supports mutli-value binding.
|
||||
*/
|
||||
@@ -1871,12 +1897,19 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new entitybean without invoking {@link BeanPostConstructListener#postCreate(Object)}
|
||||
* Creates a new entity bean without invoking {@link BeanPostConstructListener#postCreate(Object)}
|
||||
*/
|
||||
public EntityBean createEntityBean() {
|
||||
return createEntityBean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an entity bean for JSON marshalling (which differs for the element collection case).
|
||||
*/
|
||||
public EntityBean createEntityBeanForJson() {
|
||||
return createEntityBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference bean based on the id.
|
||||
*/
|
||||
@@ -1948,7 +1981,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* Return the bean property traversing the object graph and taking into
|
||||
* account inheritance.
|
||||
*/
|
||||
public BeanProperty getBeanPropertyFromPath(String path) {
|
||||
@Override
|
||||
public BeanProperty findPropertyFromPath(String path) {
|
||||
BeanDescriptor<?> other = this;
|
||||
while (true) {
|
||||
|
||||
@@ -1980,7 +2014,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
}
|
||||
String[] splitBegin = SplitName.splitBegin(path);
|
||||
|
||||
BeanProperty beanProperty = result.findBeanProperty(splitBegin[0]);
|
||||
BeanProperty beanProperty = result.findProperty(splitBegin[0]);
|
||||
if (beanProperty instanceof BeanPropertyAssoc<?>) {
|
||||
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
|
||||
path = splitBegin[1];
|
||||
@@ -2221,7 +2255,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
@Override
|
||||
public Property getProperty(String propName) {
|
||||
return findBeanProperty(propName);
|
||||
return findProperty(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2455,17 +2489,18 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
/**
|
||||
* Return a 'dynamic property' used to read a formula.
|
||||
*/
|
||||
private SqlTreeProperty findSqlTreeFormula(String formulaExpression) {
|
||||
private STreeProperty findSqlTreeFormula(String formulaExpression) {
|
||||
|
||||
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a property that is part of the SQL tree.
|
||||
*
|
||||
* <p>
|
||||
* The property can be a dynamic formula or a well known bean property.
|
||||
*/
|
||||
public SqlTreeProperty findSqlTreeProperty(String propName) {
|
||||
@Override
|
||||
public STreeProperty findPropertyWithDynamic(String propName) {
|
||||
if (propName.indexOf('(') > -1) {
|
||||
return findSqlTreeFormula(propName);
|
||||
}
|
||||
@@ -2479,7 +2514,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* inheritance tree (not up).
|
||||
* </p>
|
||||
*/
|
||||
public BeanProperty findBeanProperty(String propName) {
|
||||
@Override
|
||||
public BeanProperty findProperty(String propName) {
|
||||
int basePos = propName.indexOf('.');
|
||||
if (basePos > -1) {
|
||||
// embedded property
|
||||
@@ -2537,6 +2573,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return autoTunable;
|
||||
}
|
||||
|
||||
public boolean isElementType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Inheritance mapping information. This will be null if this type
|
||||
* of bean is not involved in any ORM inheritance mapping.
|
||||
@@ -2806,6 +2846,41 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return draftableElement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmbeddedPath(String propertyPath) {
|
||||
ElPropertyDeploy elProp = getElPropertyDeploy(propertyPath);
|
||||
if (elProp == null) {
|
||||
throw new PersistenceException("Invalid path " + propertyPath + " from " + getFullName());
|
||||
}
|
||||
return elProp.getBeanProperty().isEmbedded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtraJoin extraJoin(String propertyPath) {
|
||||
|
||||
ElPropertyValue elGetValue = getElGetValue(propertyPath);
|
||||
if (elGetValue != null) {
|
||||
BeanProperty beanProperty = elGetValue.getBeanProperty();
|
||||
if (beanProperty instanceof BeanPropertyAssoc<?>) {
|
||||
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
|
||||
if (!assocProp.isEmbedded()) {
|
||||
return new ExtraJoin(assocProp, elGetValue.containsMany());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx) {
|
||||
BeanProperty p = getBeanProperty(property.getName());
|
||||
if (p != null) {
|
||||
p.load(sqlBeanLoad);
|
||||
} else {
|
||||
property.loadIgnore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnmappedJson(EntityBean bean, Map<String, Object> unmappedProperties) {
|
||||
if (unmappedJson != null) {
|
||||
unmappedJson.setValueIntercept(bean, unmappedProperties);
|
||||
@@ -2930,16 +3005,6 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return selectLastInsertedId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the TableJoins.
|
||||
* <p>
|
||||
* For properties mapped to secondary tables rather than the base table.
|
||||
* </p>
|
||||
*/
|
||||
public TableJoin[] tableJoins() {
|
||||
return derivedTableJoins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Property> allProperties() {
|
||||
return propertiesAll();
|
||||
@@ -3128,6 +3193,26 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public STreeProperty[] propsBaseScalar() {
|
||||
return propertiesBaseScalar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public STreePropertyAssoc[] propsEmbedded() {
|
||||
return propertiesEmbedded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public STreePropertyAssocOne[] propsOne() {
|
||||
return propertiesOne;
|
||||
}
|
||||
|
||||
@Override
|
||||
public STreePropertyAssocMany[] propsMany() {
|
||||
return propertiesMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* All the BeanPropertyAssocOne that are not embedded. These are effectively
|
||||
* joined beans. For ManyToOne and OneToOne associations.
|
||||
@@ -3270,6 +3355,18 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
|
||||
}
|
||||
|
||||
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
|
||||
throw new IllegalStateException("Unexpected - expect Element override");
|
||||
}
|
||||
|
||||
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
|
||||
throw new IllegalStateException("Unexpected - expect Element override");
|
||||
}
|
||||
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
throw new IllegalStateException("Unexpected - expect Element override");
|
||||
}
|
||||
|
||||
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
|
||||
jsonHelp.jsonWrite(writeJson, bean, null);
|
||||
}
|
||||
@@ -3282,11 +3379,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
jsonHelp.jsonWriteProperties(writeJson, bean);
|
||||
}
|
||||
|
||||
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
|
||||
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
|
||||
return jsonHelp.jsonRead(jsonRead, path);
|
||||
}
|
||||
|
||||
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
|
||||
protected T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
|
||||
return jsonHelp.jsonReadObject(jsonRead, path);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -272,6 +273,11 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
|
||||
|
||||
if (many.isElementCollection()) {
|
||||
// held as part of the bean cache so skip
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedManyIds entry = manyPropGet(parentId, many.getName());
|
||||
if (entry == null) {
|
||||
// not in cache so return unsuccessful
|
||||
@@ -298,9 +304,29 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
|
||||
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
cachePutManyIds(parentId, many.getName(), entry);
|
||||
if (many.isElementCollection()) {
|
||||
CachedBeanData data = (CachedBeanData) beanCache.get(parentId);
|
||||
if (data != null) {
|
||||
try {
|
||||
// add as JSON to bean cache
|
||||
String asJson = many.jsonWriteCollection(details);
|
||||
Map<String,Object> changes = new HashMap<>();
|
||||
changes.put(many.getName(), asJson);
|
||||
|
||||
CachedBeanData newData = data.update(changes, data.getVersion());
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, parentId, changes);
|
||||
}
|
||||
beanCache.put(parentId, newData);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error updating L2 cache", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
cachePutManyIds(parentId, many.getName(), entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,38 +756,17 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
|
||||
if (manyCollections != null) {
|
||||
for (BeanPropertyAssocMany<?> many : manyCollections) {
|
||||
Object details = many.getValue(updateRequest.getEntityBean());
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
changeSet.addManyPut(desc, many.getName(), id, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if the bean itself was updated
|
||||
if (!updateRequest.isUpdatedManysOnly()) {
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
EntityBean bean = updateRequest.getEntityBean();
|
||||
boolean[] dirtyProperties = updateRequest.getDirtyProperties();
|
||||
for (int i = 0; i < dirtyProperties.length; i++) {
|
||||
if (dirtyProperties[i]) {
|
||||
BeanProperty property = desc.propertiesIndex[i];
|
||||
if (property.isCacheDataInclude()) {
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(desc, id, val);
|
||||
}
|
||||
if (!many.isElementCollection()) {
|
||||
Object details = many.getValue(updateRequest.getEntityBean());
|
||||
CachedManyIds entry = createManyIds(many, details);
|
||||
if (entry != null) {
|
||||
changeSet.addManyPut(desc, many.getName(), id, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion());
|
||||
}
|
||||
|
||||
updateRequest.addBeanUpdate(changeSet);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Bean descriptor used with ElementCollection (where we don't have a mapped type/class).
|
||||
* <p>
|
||||
* This is somewhat a BeanDescriptor created 'on the fly' for a specific element collection property
|
||||
* with a unidirectional property and mapping etc specific to the property (and not the type if embedded).
|
||||
*/
|
||||
abstract class BeanDescriptorElement<T> extends BeanDescriptor<T> {
|
||||
|
||||
final ElementHelp elementHelp;
|
||||
|
||||
BeanDescriptorElement(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
|
||||
super(owner, deploy);
|
||||
this.elementHelp = elementHelp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return the first base scalar type (and we expect only 1).
|
||||
*/
|
||||
ScalarType<Object> firstBaseScalarType() {
|
||||
BeanProperty[] props = propertiesBaseScalar();
|
||||
if (props.length != 1) {
|
||||
throw new IllegalStateException("Expecting 1 property for element scalar but got " + Arrays.toString(props));
|
||||
}
|
||||
return props[0].getScalarType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isElementType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Our entity beans used are somewhat fake ones (ElementEntityBean) such that we hold the unidirectional property
|
||||
* value (foreign key) and the actual element collection value (scalar or embedded plus map key).
|
||||
*/
|
||||
@Override
|
||||
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
|
||||
return new ElementEntityBean(properties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Bean descriptor used with element collection of list/set of embeddable.
|
||||
*/
|
||||
class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
|
||||
|
||||
private final BeanPropertyAssocOne embeddedProperty;
|
||||
|
||||
private final EntityBean prototype;
|
||||
|
||||
private BeanDescriptor targetDescriptor;
|
||||
|
||||
BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
|
||||
super(owner, deploy, elementHelp);
|
||||
try {
|
||||
this.prototype = (EntityBean) beanType.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to create entity bean prototype for "+beanType);
|
||||
}
|
||||
BeanPropertyAssocOne<?>[] embedded = propertiesEmbedded();
|
||||
if (embedded.length == 1) {
|
||||
embeddedProperty = embedded[0];
|
||||
} else {
|
||||
embeddedProperty = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialiseOther(BeanDescriptorInitContext initContext) {
|
||||
super.initialiseOther(initContext);
|
||||
this.targetDescriptor = embeddedProperty.getTargetDescriptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityBean createEntityBeanForJson() {
|
||||
return (EntityBean)prototype._ebean_newInstance();
|
||||
}
|
||||
|
||||
public void bindElementValue(SqlUpdate insert, Object value) {
|
||||
targetDescriptor.bindElementValue(insert, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
|
||||
writeJsonElement(ctx, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
|
||||
return readJsonElement(jsonRead, path);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T readJsonElement(SpiJsonReader jsonRead, String path) throws IOException {
|
||||
return (T)targetDescriptor.jsonRead(jsonRead, path);
|
||||
}
|
||||
|
||||
void writeJsonElement(SpiJsonWriter ctx, Object element) {
|
||||
try {
|
||||
if (element == null) {
|
||||
ctx.writeNull();
|
||||
} else {
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean)element);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Descriptor for element collection using Map with the value holding an embedded bean.
|
||||
* <p>
|
||||
* The expected limitation is that the key is a scalar type.
|
||||
*/
|
||||
class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<T> {
|
||||
|
||||
private final ScalarType scalarTypeKey;
|
||||
|
||||
private final boolean stringKey;
|
||||
|
||||
BeanDescriptorElementEmbeddedMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
|
||||
super(owner, deploy, elementHelp);
|
||||
this.scalarTypeKey = firstBaseScalarType();
|
||||
this.stringKey = String.class.equals(scalarTypeKey.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
|
||||
ctx.writeStartObject();
|
||||
if (stringKey) {
|
||||
Object key = entry.getKey();
|
||||
String keyName = (key == null) ? "null" : key.toString();
|
||||
ctx.writeFieldName(keyName);
|
||||
writeJsonElement(ctx, entry.getValue());
|
||||
} else {
|
||||
ctx.writeFieldName("key");
|
||||
scalarTypeKey.jsonWrite(ctx.gen(), entry.getKey());
|
||||
ctx.writeFieldName("value");
|
||||
writeJsonElement(ctx, entry.getValue());
|
||||
}
|
||||
ctx.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
JsonParser parser = readJson.getParser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
JsonToken token = parser.nextToken();
|
||||
if (token != JsonToken.START_OBJECT) {
|
||||
break;
|
||||
}
|
||||
if (stringKey) {
|
||||
String key = parser.nextFieldName();
|
||||
parser.nextToken();
|
||||
Object val = readJsonElement(readJson, null);
|
||||
add.addKeyValue(key, val);
|
||||
|
||||
} else {
|
||||
parser.nextFieldName();
|
||||
Object key = scalarTypeKey.jsonRead(parser);
|
||||
|
||||
parser.nextFieldName();
|
||||
Object val = readJsonElement(readJson, null);
|
||||
add.addKeyValue(key, val);
|
||||
}
|
||||
|
||||
token = parser.nextToken();
|
||||
if (token != JsonToken.END_OBJECT) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
return add.collection();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Bean descriptor used with element collection mapped to a list or set of scalar values.
|
||||
*/
|
||||
class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
|
||||
|
||||
private final ScalarType<Object> scalarType;
|
||||
|
||||
BeanDescriptorElementScalar(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
|
||||
super(owner, deploy, elementHelp);
|
||||
this.scalarType = firstBaseScalarType();
|
||||
}
|
||||
|
||||
public void bindElementValue(SqlUpdate insert, Object value) {
|
||||
insert.setNextParameter(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
|
||||
try {
|
||||
scalarType.jsonWrite(ctx.gen(), element);
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
JsonParser parser = readJson.getParser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
break;
|
||||
}
|
||||
add.addElement(scalarType.jsonRead(parser));
|
||||
} while (true);
|
||||
|
||||
return add.collection();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user