mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
151
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a75e1f66c | ||
|
|
47ac4c5c5b | ||
|
|
4308a98a0a | ||
|
|
f86f1b2647 | ||
|
|
59537227b6 | ||
|
|
81c6d4601d | ||
|
|
e21839de91 | ||
|
|
a61db2ab35 | ||
|
|
bddfa3bc5f | ||
|
|
9ad460ecd8 | ||
|
|
50593a7f9d | ||
|
|
c088cfa339 | ||
|
|
e683e25a95 | ||
|
|
c7052c5bfa | ||
|
|
698ee2198d | ||
|
|
91c4eca528 | ||
|
|
81897bd54d | ||
|
|
2427630c07 | ||
|
|
3238c0def4 | ||
|
|
93f75151f0 | ||
|
|
32e9b48359 | ||
|
|
258e29d2f0 | ||
|
|
ec2f1f7243 | ||
|
|
5775985468 | ||
|
|
4788dd4ec9 | ||
|
|
ca7e5b79b8 | ||
|
|
90dfbcd7b3 | ||
|
|
6b39bad4da | ||
|
|
25dea0b5b0 | ||
|
|
0069d2fabc | ||
|
|
98964638ba | ||
|
|
4573a995f1 | ||
|
|
4bc80cc724 | ||
|
|
a2972abe3e | ||
|
|
54eaf561f4 | ||
|
|
3134dd3165 | ||
|
|
8fc6abd480 | ||
|
|
f031bb72b6 | ||
|
|
42f0b419e8 | ||
|
|
7cbae758fc | ||
|
|
fb32cfa170 | ||
|
|
ffa8268125 | ||
|
|
eba1003c19 | ||
|
|
eca6383dba | ||
|
|
624d08f46c | ||
|
|
14749daf9b | ||
|
|
2817875706 | ||
|
|
3f8d705439 | ||
|
|
e9faef2692 | ||
|
|
d459807af7 | ||
|
|
10ad9ef096 | ||
|
|
11e3649b85 | ||
|
|
ddc00fedb6 | ||
|
|
0aa9208dcc | ||
|
|
e3b1996dc9 | ||
|
|
24ca744e06 | ||
|
|
7f9ce5bdd4 | ||
|
|
23e5de0819 | ||
|
|
c23508819f | ||
|
|
af030e3da4 | ||
|
|
d8e67d09a9 | ||
|
|
66c08e663e | ||
|
|
9a096e272e | ||
|
|
21e87e12cb | ||
|
|
7c6d7dd4d0 | ||
|
|
c5329e9db0 | ||
|
|
bccb9f8ddb | ||
|
|
c57af29fca | ||
|
|
37b6a321ab | ||
|
|
896f7d9ea8 | ||
|
|
141a0d9358 | ||
|
|
5a7f24da5d | ||
|
|
8ea86d6c6d | ||
|
|
fb5eeb60b1 | ||
|
|
33ec8abb9d | ||
|
|
bbd7baf1be | ||
|
|
2e36402a76 | ||
|
|
fa4af4ef98 | ||
|
|
ed6a9e2dbb | ||
|
|
42d83705b7 | ||
|
|
ae58d9223a | ||
|
|
a9a89938f9 | ||
|
|
4b91f5fe0b | ||
|
|
85d3caf079 | ||
|
|
f6d8c8b618 | ||
|
|
d0fa3f0cb1 | ||
|
|
41a31c1aa0 | ||
|
|
ab301f970c | ||
|
|
d1b7b082a2 | ||
|
|
5fb3a1e019 | ||
|
|
cb0dee998b | ||
|
|
53b606289d | ||
|
|
574d5e215b | ||
|
|
508f2129f5 | ||
|
|
cdfcbeec43 | ||
|
|
0e9782e6b3 | ||
|
|
927fa5f7d3 | ||
|
|
53a80c7dd1 | ||
|
|
89789f3347 | ||
|
|
9dc5ca602c | ||
|
|
95737cd3c5 | ||
|
|
0a49fc9b62 | ||
|
|
f8c80c7fd2 | ||
|
|
fc837b6472 | ||
|
|
cfaedcdbc5 | ||
|
|
111bac7514 | ||
|
|
1a2ca63d65 | ||
|
|
33031d1c69 | ||
|
|
49d9d88fbe | ||
|
|
756f867ed2 | ||
|
|
12108ada38 | ||
|
|
fb2fdc9b53 | ||
|
|
d570971fd1 | ||
|
|
a8434f22ff | ||
|
|
b0d3921264 | ||
|
|
4167dfc4af | ||
|
|
e49a0c9ffb | ||
|
|
dd6f223ae1 | ||
|
|
05aff53241 | ||
|
|
9a3362e842 | ||
|
|
04e0069eb3 | ||
|
|
b8c4e1c3d3 | ||
|
|
6b136248fa | ||
|
|
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 |
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
|
||||
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
|
||||
|
||||
## Current versions
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
|
||||
|
||||
@@ -9,15 +9,20 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.15.1</version>
|
||||
<version>11.17.5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<jackson-core.version>2.9.5</jackson-core.version>
|
||||
<snakeyaml.version>1.21</snakeyaml.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.15.1</tag>
|
||||
<tag>ebean-11.17.5</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -89,7 +94,7 @@
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.19</version>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
@@ -118,7 +123,7 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -130,7 +135,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.5.1</version>
|
||||
<version>11.5.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -143,14 +148,14 @@
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -224,7 +229,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 +259,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 +302,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>
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
db().save(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans in the collection.
|
||||
*/
|
||||
public int saveAll(Collection<T> bean) {
|
||||
return db().saveAll(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*
|
||||
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
*/
|
||||
public int deleteAll(Collection<T> beans) {
|
||||
return db().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
* <p>
|
||||
@@ -167,6 +183,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the default merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object)
|
||||
*/
|
||||
public void merge(T bean) {
|
||||
db().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the specified merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object, MergeOptions)
|
||||
*/
|
||||
public void merge(T bean, MergeOptions options) {
|
||||
db().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*
|
||||
|
||||
@@ -638,6 +638,15 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
public static void merge(Object bean) {
|
||||
serverMgr.getDefaultServer().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
@@ -795,22 +808,41 @@ public interface EbeanServer {
|
||||
<T> T getReference(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Return the extended API for EbeanServer.
|
||||
* <p>
|
||||
* The extended API has the options for executing queries that take an explicit
|
||||
* transaction as an argument.
|
||||
* </p>
|
||||
* <p>
|
||||
* Typically we only need to use the extended API when we do NOT want to use the
|
||||
* usual ThreadLocal based mechanism to obtain the current transaction but instead
|
||||
* supply the transaction explicitly.
|
||||
* </p>
|
||||
*/
|
||||
ExtendedServer extended();
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Deprecated
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Return a QueryIterator for the query.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
@@ -828,9 +860,11 @@ public interface EbeanServer {
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
@@ -858,9 +892,11 @@ public interface EbeanServer {
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Deprecated
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
@@ -895,9 +931,11 @@ public interface EbeanServer {
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Deprecated
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
@@ -905,9 +943,11 @@ public interface EbeanServer {
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
@@ -931,9 +971,11 @@ public interface EbeanServer {
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
@@ -947,9 +989,11 @@ public interface EbeanServer {
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
@@ -963,9 +1007,11 @@ public interface EbeanServer {
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
@@ -980,9 +1026,11 @@ public interface EbeanServer {
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
@@ -1012,9 +1060,11 @@ public interface EbeanServer {
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
@@ -1038,9 +1088,11 @@ public interface EbeanServer {
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
@@ -1055,9 +1107,11 @@ public interface EbeanServer {
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
@@ -1088,9 +1142,11 @@ public interface EbeanServer {
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
@@ -1110,15 +1166,19 @@ public interface EbeanServer {
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
@@ -1131,9 +1191,11 @@ public interface EbeanServer {
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
@Deprecated
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link #update(Class)}.
|
||||
@@ -1144,9 +1206,11 @@ public interface EbeanServer {
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
@Deprecated
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
@@ -1160,17 +1224,21 @@ public interface EbeanServer {
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
@@ -1179,9 +1247,11 @@ public interface EbeanServer {
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Deprecated - Moved to the ExtendedServer API, please use via extended().
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
@@ -1199,6 +1269,7 @@ public interface EbeanServer {
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1615,6 +1686,13 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options (no paths specified, default delete).
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
void merge(Object bean);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Class to determine the ebean version. (
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class EbeanVersion {
|
||||
private EbeanVersion() {
|
||||
|
||||
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
|
||||
|
||||
|
||||
private static String version = "unknown";
|
||||
static {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
}
|
||||
}
|
||||
logger.info("ebean version: {}", version);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not determine ebean version: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
@@ -482,7 +490,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
@@ -878,7 +886,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)
|
||||
*/
|
||||
@@ -888,7 +896,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)
|
||||
*/
|
||||
@@ -898,7 +906,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)
|
||||
*/
|
||||
@@ -996,6 +1004,67 @@ 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 expression for the given bit flags to be NOT set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseNot(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,452 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The extended API for EbeanServer.
|
||||
* <p>
|
||||
* This provides the finder methods that take an explicit transaction rather than obtaining
|
||||
* the transaction from the usual mechanism (which is ThreadLocal based).
|
||||
* </p>
|
||||
* <p>
|
||||
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
|
||||
* the use of the mechanism to get the current transaction and instead explicitly supply
|
||||
* the transaction to use.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
|
||||
* will use the normal mechanism to obtain the transaction to use.
|
||||
* </p>
|
||||
*/
|
||||
public interface ExtendedServer {
|
||||
|
||||
/**
|
||||
* Return the NOW time from the Clock.
|
||||
*/
|
||||
long clockNow();
|
||||
|
||||
/**
|
||||
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
|
||||
* <p>
|
||||
* Note that we only expect to change the Clock for testing purposes.
|
||||
* </p>
|
||||
*/
|
||||
void setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link EbeanServer#update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
}
|
||||
@@ -8,11 +8,20 @@ import java.util.Set;
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private static final MOptions DEFAULT_OPTIONS = new MOptions();
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent = true;
|
||||
private boolean deletePermanent;
|
||||
|
||||
/**
|
||||
* Return the default options.
|
||||
*/
|
||||
public static MergeOptions defaultOptions() {
|
||||
return DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MOptions build() {
|
||||
public MergeOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
@@ -57,6 +66,12 @@ public class MergeOptionsBuilder {
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(){
|
||||
this.clientGeneratedIds = false;
|
||||
this.paths = new LinkedHashSet<>();
|
||||
this.deletePermanent = false;
|
||||
}
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
|
||||
@@ -76,7 +76,6 @@ class PrimaryServer {
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = "db";
|
||||
}
|
||||
System.setProperty("ebean_db", defaultServerName);
|
||||
return defaultServerName;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowConsumer {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and process it.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
void accept(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to map raw JDBC ResultSet to objects.
|
||||
* <p>
|
||||
* This provides a low level mapping option with direct use of JDBC ResultSet
|
||||
* with the option of having logic in the mapping. For example, only map some
|
||||
* columns depending on the values read from other columns.
|
||||
* </p>
|
||||
* <p>
|
||||
* For straight mapping into beans then DtoQuery would be the first choice as
|
||||
* it can automatically map the ResultSet into beans.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* //
|
||||
* // A mapper from ResultSet into our CustomerDto bean
|
||||
* //
|
||||
* class CustomerMapper implements RowMapper<CustomerDto> {
|
||||
*
|
||||
* @Override
|
||||
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
|
||||
*
|
||||
* long id = rset.getLong(1);
|
||||
* String name = rset.getString(2);
|
||||
* String status = rset.getString(3);
|
||||
*
|
||||
* return new CustomerDto(id, name, status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* //
|
||||
* // Then use the mapper
|
||||
* //
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer where name = ?";
|
||||
*
|
||||
* CustomerDto rob = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, "Rob")
|
||||
* .findOne(CUSTOMER_MAPPER);
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The type the row data is mapped into.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and map to the return type.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
T map(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package io.ebean;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> T findOne(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> List<T> findList(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query reading each row from ResultSet using the RowConsumer.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet
|
||||
* and is good for processing very large results where (unlike findList) we don't
|
||||
* hold all the results in memory but instead can process row by row.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer Used to read and process each ResultSet row.
|
||||
*/
|
||||
void findEachRow(RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single scalar attribute.
|
||||
* <pre>@{code
|
||||
*
|
||||
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
|
||||
*
|
||||
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 2)
|
||||
* .findSingleAttribute(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> T findSingleAttribute(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single BigDecimal value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
BigDecimal findSingleDecimal();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single Long value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(Long.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
Long findSingleLong();
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of scalar attribute values.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql =
|
||||
* " select (unit_price * order_qty) " +
|
||||
* " from o_order_detail " +
|
||||
* " where unit_price > ? " +
|
||||
* " order by (unit_price * order_qty) desc";
|
||||
*
|
||||
* //
|
||||
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 3)
|
||||
* .findSingleAttributeList(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
|
||||
@@ -19,15 +19,73 @@ package io.ebean;
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // example that uses 'named' parameters
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
* // example that uses 'named' parameters
|
||||
*
|
||||
* int modifiedCount = Ebean.execute(update);
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
*
|
||||
* int modifiedCount = update.execute();
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <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>
|
||||
* <p>
|
||||
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* int[] rows = insert.executeBatch();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -54,6 +112,18 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
* @return The row counts for each of the batched statements.
|
||||
*/
|
||||
int[] executeBatch();
|
||||
|
||||
/**
|
||||
* Add the statement to batch processing to then later execute via executeBatch().
|
||||
*/
|
||||
void addBatch();
|
||||
|
||||
/**
|
||||
* Return the generated key value.
|
||||
*/
|
||||
@@ -61,7 +131,7 @@ public interface SqlUpdate {
|
||||
|
||||
/**
|
||||
* Execute and return the generated key. This is effectively a short cut for:
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* sqlUpdate.execute();
|
||||
@@ -137,6 +207,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.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* This returns the current transaction for the 'default server'. If you are using
|
||||
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
|
||||
* </p>
|
||||
*
|
||||
* @see Ebean#currentTransaction()
|
||||
* @see EbeanServer#currentTransaction()
|
||||
*/
|
||||
@@ -136,6 +137,18 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void rollback(Throwable e) throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Set when we want nested transactions to use Savepoint's.
|
||||
* <p>
|
||||
* This means that for a nested transaction:
|
||||
* <ul>
|
||||
* <li>begin transaction maps to creating a savepoint</li>
|
||||
* <li>commit transaction maps to releasing a savepoint</li>
|
||||
* <li>rollback transaction maps to rollback a savepoint</li>
|
||||
* </ul>
|
||||
*/
|
||||
void setNestedUseSavepoint();
|
||||
|
||||
/**
|
||||
* Mark the transaction for rollback only.
|
||||
*/
|
||||
@@ -151,7 +164,6 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void end();
|
||||
|
||||
|
||||
/**
|
||||
* Synonym for end() to support AutoClosable.
|
||||
*/
|
||||
@@ -227,7 +239,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
|
||||
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // assume Customer has L2 bean caching enabled ...
|
||||
@@ -309,7 +321,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* Example: batch processing executing every 3 rows
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String data = "This is a simple test of the batch processing"
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -133,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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -402,9 +402,20 @@ public class DbMigrationConfig {
|
||||
* You can use placeholders like ${version} or ${timestamp} in properties file.
|
||||
*/
|
||||
public String getDdlHeader() {
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
return ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header prepended to the DDL.
|
||||
*/
|
||||
public void setDdlHeader(String ddlHeader) {
|
||||
this.ddlHeader = ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
@@ -475,10 +486,6 @@ public class DbMigrationConfig {
|
||||
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,6 +33,7 @@ import io.ebean.util.StringHelper;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -57,22 +58,24 @@ import java.util.ServiceLoader;
|
||||
* <pre>{@code
|
||||
*
|
||||
* ServerConfig c = new ServerConfig();
|
||||
* c.setName("db");
|
||||
*
|
||||
* // read the ebean.properties and load
|
||||
* // those settings into this serverConfig object
|
||||
* c.loadFromProperties();
|
||||
*
|
||||
* // add any classes found in the app.data package
|
||||
* c.addPackage("com.myapp.domain");
|
||||
*
|
||||
* // register as the 'Default' server
|
||||
* c.setDefaultServer(true);
|
||||
* // explicitly register the entity beans to avoid classpath scanning
|
||||
* c.addClass(Customer.class);
|
||||
* c.addClass(User.class);
|
||||
*
|
||||
* EbeanServer server = EbeanServerFactory.create(c);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
|
||||
* be used to apply configuration to the ServerConfig.
|
||||
* </p>
|
||||
*
|
||||
* @author emcgreal
|
||||
* @author rbygrave
|
||||
* @see EbeanServerFactory
|
||||
@@ -360,6 +363,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
private List<IdGenerator> idGenerators = new ArrayList<>();
|
||||
private List<BeanFindController> findControllers = new ArrayList<>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<>();
|
||||
@@ -488,6 +496,16 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean defaultOrderById = false;
|
||||
|
||||
/**
|
||||
* The mappingLocations for searching xml mapping.
|
||||
*/
|
||||
private List<String> mappingLocations = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* When true we do not need explicit GeneratedValue mapping.
|
||||
*/
|
||||
private boolean idGeneratorAutomatic = true;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -495,6 +513,20 @@ public class ServerConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public void setClock(final Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query time in millis.
|
||||
*/
|
||||
@@ -1962,7 +1994,7 @@ public class ServerConfig {
|
||||
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
|
||||
* as it is often the only thing we need to configure for migrations.
|
||||
*/
|
||||
public void setRunMigration(boolean runMigration){
|
||||
public void setRunMigration(boolean runMigration) {
|
||||
migrationConfig.setRunMigration(runMigration);
|
||||
}
|
||||
|
||||
@@ -2767,6 +2799,7 @@ public class ServerConfig {
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
@@ -2780,10 +2813,8 @@ public class ServerConfig {
|
||||
dbOffline = p.getBoolean("dbOffline", dbOffline);
|
||||
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);
|
||||
@@ -2862,10 +2893,13 @@ public class ServerConfig {
|
||||
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 = properties.createInstance(NamingConvention.class, "namingconvention", null);
|
||||
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingConvention", null);
|
||||
return (nc != null) ? nc : namingConvention;
|
||||
}
|
||||
|
||||
@@ -2898,17 +2932,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3076,6 +3110,50 @@ public class ServerConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 mapping locations to search for xml mapping via class path search.
|
||||
*/
|
||||
public List<String> getMappingLocations() {
|
||||
return mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
|
||||
* Identity or Sequence generated values. When true Id properties are automatically
|
||||
* assigned Identity or Sequence without the GeneratedValue mapping.
|
||||
*/
|
||||
public boolean isIdGeneratorAutomatic() {
|
||||
return idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
|
||||
* mapping before they are assigned Identity or Sequence generation based on platform.
|
||||
*/
|
||||
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
|
||||
* <p>
|
||||
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
|
||||
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
|
||||
* spring specific configuration. When we are not using a DI container we may use this mechanism to
|
||||
* explicitly register the entity beans and avoid classpath scanning.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class EbeanConfigProvider implements ServerConfigProvider {
|
||||
*
|
||||
* @Override
|
||||
* public void apply(ServerConfig config) {
|
||||
*
|
||||
* // register the entity bean classes explicitly
|
||||
* config.addClass(Customer.class);
|
||||
* config.addClass(User.class);
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ServerConfigProvider {
|
||||
|
||||
/**
|
||||
* Apply the configuration to the ServerConfig.
|
||||
* <p>
|
||||
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
|
||||
* </p>
|
||||
*/
|
||||
void apply(ServerConfig config);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,26 +18,26 @@ import java.util.Set;
|
||||
*/
|
||||
class LoadContext {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
|
||||
/**
|
||||
* Map we are loading the properties into.
|
||||
*/
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Names of resources/files that were loaded.
|
||||
*/
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the input stream (maybe null) for the given source.
|
||||
*/
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
|
||||
InputStream is = null;
|
||||
if (source == Loader.Source.RESOURCE) {
|
||||
is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
is = resourceStream(resourcePath);
|
||||
if (is != null) {
|
||||
loadedResources.add(resourcePath);
|
||||
}
|
||||
@@ -46,43 +46,52 @@ class LoadContext {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
is = new FileInputStream(file);
|
||||
loadedResources.add("file:"+resourcePath);
|
||||
loadedResources.add("file:" + resourcePath);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is;
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
private InputStream resourceStream(String resourcePath) {
|
||||
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
if (is == null) {
|
||||
// search the module path for top level resource
|
||||
is = ClassLoader.getSystemResourceAsStream(resourcePath);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property entry.
|
||||
*/
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all the expressions and return as a Properties object.
|
||||
*/
|
||||
Properties eval() {
|
||||
Properties eval() {
|
||||
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
|
||||
Properties properties = new Properties();
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the special properties that can point to an external properties source.
|
||||
|
||||
@@ -16,10 +16,10 @@ import java.util.Properties;
|
||||
*/
|
||||
class Loader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
FILE
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class Loader {
|
||||
|
||||
private YamlLoader yamlLoader;
|
||||
|
||||
Loader() {
|
||||
Loader() {
|
||||
String skipYaml = System.getProperty("ebeanSkipYaml");
|
||||
if (!"true".equals(skipYaml)) {
|
||||
try {
|
||||
@@ -44,19 +44,19 @@ class Loader {
|
||||
/**
|
||||
* Load the configuration with the expected ordering.
|
||||
*/
|
||||
void load() {
|
||||
void load() {
|
||||
|
||||
loadMain(Source.RESOURCE);
|
||||
// external file configuration overrides the resources configuration
|
||||
loadMain(Source.FILE);
|
||||
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
|
||||
// test configuration (if found) overrides main configuration
|
||||
// test configuration (if found) overrides main configuration
|
||||
// we should only find these resources when running tests
|
||||
loadTest();
|
||||
}
|
||||
loadTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load test configuration.
|
||||
@@ -89,7 +89,7 @@ class Loader {
|
||||
}
|
||||
|
||||
private void loadViaSystemProperty() {
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName != null) {
|
||||
@@ -112,59 +112,66 @@ class Loader {
|
||||
* Evaluate all the configuration entries and return as properties.
|
||||
*/
|
||||
Properties eval() {
|
||||
return loadContext.eval();
|
||||
}
|
||||
return loadContext.eval();
|
||||
}
|
||||
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
yamlLoader.load(resource(resourcePath, source));
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
yamlLoader.load(is);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read yml from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
InputStream is = resource(resourcePath, source);
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read properties from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
|
||||
private void loadProperties(InputStream is) {
|
||||
private void loadProperties(InputStream is) {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,38 @@ public interface DbMigration {
|
||||
*/
|
||||
void setStrictMode(boolean strictMode);
|
||||
|
||||
/**
|
||||
* Set to true to include a generated header comment in the DDL script.
|
||||
*/
|
||||
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
|
||||
|
||||
/**
|
||||
* Set the header that is included in the generated DDL script.
|
||||
*/
|
||||
void setHeader(String header);
|
||||
|
||||
/**
|
||||
* Set the prefix for the version. Set this to "V" for use with Flyway.
|
||||
*/
|
||||
void setApplyPrefix(String applyPrefix);
|
||||
|
||||
/**
|
||||
* Set the version of the migration to be generated.
|
||||
*/
|
||||
void setVersion(String version);
|
||||
|
||||
/**
|
||||
* Set the name of the migration to be generated.
|
||||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Generate a migration for the version specified that contains pending drops.
|
||||
*
|
||||
* @param generatePendingDrop The version of a prior migration that holds pending drops.
|
||||
*/
|
||||
void setGeneratePendingDrop(String generatePendingDrop);
|
||||
|
||||
/**
|
||||
* Add an additional platform to write the migration DDL.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ebean.event;
|
||||
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
|
||||
/**
|
||||
* Listens for webserver server starting and stopping events.
|
||||
* <p>
|
||||
* This should be used when the deployment is into a servlet container where the webapp
|
||||
* can be shutdown or redeployed without the JVM stopping.
|
||||
* </p>
|
||||
* <p>
|
||||
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
|
||||
* runnable war or when using a servlet container that only contains the single webapp and
|
||||
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
|
||||
* hook that Ebean registers.
|
||||
* </p>
|
||||
*/
|
||||
public class ServletContextListener implements javax.servlet.ServletContextListener {
|
||||
|
||||
/**
|
||||
* The servlet container is stopping.
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent event) {
|
||||
ShutdownManager.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing on startup.
|
||||
*/
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent event) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
|
||||
}
|
||||
|
||||
Props getProps(String path) {
|
||||
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
return props;
|
||||
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
}
|
||||
|
||||
public Collection<Props> getPathProps() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -53,6 +53,10 @@ public class ScopeTrans {
|
||||
*/
|
||||
private boolean rolledBack;
|
||||
|
||||
/**
|
||||
* Flag set when nested commit has occurred.
|
||||
*/
|
||||
private boolean nestedCommit;
|
||||
|
||||
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
|
||||
|
||||
@@ -113,7 +117,6 @@ public class ScopeTrans {
|
||||
complete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete the transaction programmatically. Try to commit.
|
||||
*/
|
||||
@@ -124,7 +127,7 @@ public class ScopeTrans {
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if (created) {
|
||||
if (created || !nestedCommit) {
|
||||
transaction.end();
|
||||
}
|
||||
}
|
||||
@@ -133,6 +136,7 @@ public class ScopeTrans {
|
||||
if (created) {
|
||||
transaction.commit();
|
||||
} else {
|
||||
nestedCommit = true;
|
||||
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
|
||||
if (restoreBatch != null) {
|
||||
transaction.setBatch(restoreBatch);
|
||||
|
||||
@@ -2,8 +2,11 @@ package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.ExtendedServer;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
@@ -29,7 +32,12 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Service Provider extension to EbeanServer.
|
||||
*/
|
||||
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
|
||||
/**
|
||||
* Return the log manager.
|
||||
*/
|
||||
SpiLogManager log();
|
||||
|
||||
/**
|
||||
* Return the server extended Json context.
|
||||
@@ -229,6 +237,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
void scopedTransactionExit(Object returnOrThrowable, int opCode);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute.
|
||||
*/
|
||||
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute list.
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find one with mapper.
|
||||
*/
|
||||
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find list with mapper.
|
||||
*/
|
||||
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find each with consumer.
|
||||
*/
|
||||
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* DTO findList query.
|
||||
*/
|
||||
@@ -268,4 +301,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
* Return true if a row for the bean type and id exists.
|
||||
*/
|
||||
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Add to JDBC batch for later execution.
|
||||
*/
|
||||
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the batched statement.
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Log manager for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogManager {
|
||||
|
||||
/**
|
||||
* Return the SQL logger.
|
||||
*/
|
||||
SpiLogger sql();
|
||||
|
||||
/**
|
||||
* Return the TXN logger.
|
||||
*/
|
||||
SpiLogger txn();
|
||||
|
||||
/**
|
||||
* Return the Summary logger.
|
||||
*/
|
||||
SpiLogger sum();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Logger for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogger {
|
||||
|
||||
/**
|
||||
* Is debug logging enabled.
|
||||
*/
|
||||
boolean isDebug();
|
||||
|
||||
/**
|
||||
* Is trace logging enabled.
|
||||
*/
|
||||
boolean isTrace();
|
||||
|
||||
/**
|
||||
* Log a debug level message.
|
||||
*/
|
||||
void debug(String msg);
|
||||
|
||||
/**
|
||||
* Log a trace level message.
|
||||
*/
|
||||
void trace(String msg);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
|
||||
*/
|
||||
public interface SpiLoggerFactory {
|
||||
|
||||
/**
|
||||
* Create the logger given the name.
|
||||
*/
|
||||
SpiLogger create(String name);
|
||||
}
|
||||
@@ -644,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
boolean isBeanCachePut();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
|
||||
* Return true if we must hit the DB (Cache reload or select for update).
|
||||
*/
|
||||
boolean isBeanCacheReload();
|
||||
boolean isForceHitDatabase();
|
||||
|
||||
/**
|
||||
* Return the cache mode for using the bean cache (Get and Put).
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -319,4 +319,9 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return the profile location for this transaction.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true when nested transactions should create Savepoints.
|
||||
*/
|
||||
boolean isNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.sql.SQLException;
|
||||
/**
|
||||
* Proxy for an underlying SpiTransaction (most of the API).
|
||||
*/
|
||||
abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
|
||||
protected SpiTransaction transaction;
|
||||
|
||||
@@ -50,6 +50,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNestedUseSavepoint() {
|
||||
transaction.setNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNestedUseSavepoint() {
|
||||
return transaction.isNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long profileOffset() {
|
||||
return transaction.profileOffset();
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
|
||||
String EVT_INSERT = "i";
|
||||
String EVT_UPDATE = "u";
|
||||
String EVT_DELETE = "d";
|
||||
String EVT_SOFT_DELETE = "ds";
|
||||
String EVT_DELETE_SOFT = "ds";
|
||||
String EVT_DELETE_PERMANENT = "dp";
|
||||
String EVT_ORMUPDATE = "uo";
|
||||
String FIND_UPDATE = "uq";
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -2,10 +2,10 @@ package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -173,10 +173,11 @@ public class DdlGenerator {
|
||||
protected void runResourceScript(String sqlScript) throws IOException {
|
||||
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(false, content, sqlScript);
|
||||
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(false, content, sqlScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,12 @@ public class DefaultDbMigration implements DbMigration {
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
protected Boolean strictMode;
|
||||
protected Boolean includeGeneratedFileComment;
|
||||
protected String header;
|
||||
protected String applyPrefix;
|
||||
protected String version;
|
||||
protected String name;
|
||||
protected String generatePendingDrop;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
@@ -148,6 +154,36 @@ public class DefaultDbMigration implements DbMigration {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplyPrefix(String applyPrefix) {
|
||||
this.applyPrefix = applyPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratePendingDrop(String generatePendingDrop) {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
@@ -512,8 +548,28 @@ public class DefaultDbMigration implements DbMigration {
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
if (strictMode != null && migrationConfig != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
if (migrationConfig != null) {
|
||||
if (strictMode != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
}
|
||||
if (applyPrefix != null) {
|
||||
migrationConfig.setApplyPrefix(applyPrefix);
|
||||
}
|
||||
if (header != null) {
|
||||
migrationConfig.setDdlHeader(header);
|
||||
}
|
||||
if (includeGeneratedFileComment != null) {
|
||||
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
|
||||
}
|
||||
if (version != null) {
|
||||
migrationConfig.setVersion(version);
|
||||
}
|
||||
if (name != null) {
|
||||
migrationConfig.setName(name);
|
||||
}
|
||||
if (generatePendingDrop != null) {
|
||||
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ public class DdlWrite {
|
||||
|
||||
private final DdlBuffer applyForeignKeys;
|
||||
|
||||
private final DdlBuffer applyHistory;
|
||||
private final DdlBuffer applyHistoryView;
|
||||
|
||||
private final DdlBuffer applyHistoryTrigger;
|
||||
|
||||
private final DdlBuffer dropAllForeignKeys;
|
||||
|
||||
@@ -39,7 +41,8 @@ public class DdlWrite {
|
||||
this.applyDropDependencies = new BaseDdlBuffer(configuration);
|
||||
this.apply = new BaseDdlBuffer(configuration);
|
||||
this.applyForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.applyHistory = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryView = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
|
||||
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.dropAll = new BaseDdlBuffer(configuration);
|
||||
}
|
||||
@@ -61,7 +64,8 @@ public class DdlWrite {
|
||||
public boolean isApplyEmpty() {
|
||||
return apply.getBuffer().isEmpty()
|
||||
&& applyForeignKeys.getBuffer().isEmpty()
|
||||
&& applyHistory.getBuffer().isEmpty()
|
||||
&& applyHistoryView.getBuffer().isEmpty()
|
||||
&& applyHistoryTrigger.getBuffer().isEmpty()
|
||||
&& applyDropDependencies.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
@@ -90,10 +94,17 @@ public class DdlWrite {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history DDL is written to.
|
||||
* Return the buffer that apply history-view DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistory() {
|
||||
return applyHistory;
|
||||
public DdlBuffer applyHistoryView() {
|
||||
return applyHistoryView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history-trigger DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistoryTrigger() {
|
||||
return applyHistoryTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+47
-18
@@ -42,6 +42,12 @@ import java.util.Map;
|
||||
*/
|
||||
public class BaseTableDdl implements TableDdl {
|
||||
|
||||
enum HistorySupport {
|
||||
NONE,
|
||||
SQL2011,
|
||||
TRIGGER_BASED
|
||||
}
|
||||
|
||||
protected final DbConstraintNaming naming;
|
||||
|
||||
protected final NamingConvention namingConvention;
|
||||
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
private boolean strictMode;
|
||||
|
||||
private final boolean sql2011History;
|
||||
private final HistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* Helper class that is used to execute the migration ddl before and after the migration action.
|
||||
*/
|
||||
private class DdlMigrationHelp {
|
||||
private List<String> before;
|
||||
private List<String> after;
|
||||
private String tableName;
|
||||
private String columnName;
|
||||
private String defaultValue;
|
||||
private final List<String> before;
|
||||
private final List<String> after;
|
||||
private final String tableName;
|
||||
private final String columnName;
|
||||
private final String defaultValue;
|
||||
private final boolean withHistory;
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when adding a NEW column.
|
||||
*/
|
||||
DdlMigrationHelp(String tableName, Column column) {
|
||||
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
|
||||
this.tableName = tableName;
|
||||
this.columnName = column.getName();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
|
||||
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
|
||||
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
|
||||
this.withHistory = withHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
|
||||
// here we add the platform's default update script
|
||||
withHistory = isTrue(alter.isWithHistory());
|
||||
if (alter.getBefore().isEmpty() && alterNotNull) {
|
||||
if (defaultValue == null) {
|
||||
handleStrictError(tableName, columnName);
|
||||
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (!before.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
|
||||
if (!before.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
for (String ddlScript : before) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
|
||||
buffer.endOfStatement();
|
||||
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
|
||||
void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
if (!after.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
// here we run post migration scripts
|
||||
for (String ddlScript : after) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
|
||||
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
|
||||
this.platformDdl.configure(serverConfig);
|
||||
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
|
||||
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
|
||||
this.sql2011History = hist != null && hist.isStandardsBased();
|
||||
if (hist == null) {
|
||||
this.historySupport = HistorySupport.NONE;
|
||||
} else {
|
||||
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -700,8 +720,14 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
@Override
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
if (!regenerateHistoryTriggers.isEmpty()) {
|
||||
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
}
|
||||
|
||||
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
}
|
||||
platformDdl.generateEpilog(write);
|
||||
}
|
||||
@@ -722,15 +748,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = addColumn.getTableName();
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false);
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
|
||||
}
|
||||
|
||||
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
for (Column column : columns) {
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true);
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,7 +797,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = dropColumn.getTableName();
|
||||
|
||||
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
|
||||
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
|
||||
|
||||
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// also drop from the history table
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
|
||||
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
|
||||
@@ -872,8 +899,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
|
||||
alterHistoryColumn.setColumnName(alter.getColumnName());
|
||||
@@ -923,7 +951,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
if (isTrue(alter.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
// apply same type change to matching column in the history table
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
@@ -979,8 +1008,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
|
||||
if (!onHistoryTable) {
|
||||
help.writeBefore(buffer);
|
||||
}
|
||||
|
||||
+14
-14
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
*/
|
||||
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
|
||||
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
|
||||
|
||||
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
|
||||
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
|
||||
createHistoryTable(apply, table);
|
||||
@@ -210,19 +210,19 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
.endOfStatement().end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or replace the with_history view with explicit columns.
|
||||
*/
|
||||
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
|
||||
appendColumnNames(apply, update.getColumns(), "");
|
||||
appendSysPeriodColumns(apply, ", ");
|
||||
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
|
||||
appendColumnNames(apply, update.getColumns(), "");
|
||||
appendSysPeriodColumns(apply, ", ");
|
||||
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
|
||||
/**
|
||||
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
|
||||
* but otherwise we need to drop and create it.
|
||||
*/
|
||||
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.dropDependencyBuffer();
|
||||
// we need to drop the view early/first before any changes to the tables etc
|
||||
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
|
||||
|
||||
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
|
||||
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
|
||||
}
|
||||
|
||||
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
|
||||
|
||||
+10
-2
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyBuffer() {
|
||||
return writer.applyHistory();
|
||||
public DdlBuffer historyViewBuffer() {
|
||||
return writer.applyHistoryView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyTriggerBuffer() {
|
||||
return writer.applyHistoryTrigger();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the appropriate drop dependency buffer for the current mode.
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryTrigger();
|
||||
|
||||
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
|
||||
}
|
||||
@@ -35,7 +35,9 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
@Override
|
||||
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.historyBuffer();
|
||||
recreateHistoryView(update);
|
||||
|
||||
DdlBuffer buffer = update.historyTriggerBuffer();
|
||||
dropTriggers(buffer, update.getBaseTable());
|
||||
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
|
||||
}
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@ public class HistoryTableUpdate {
|
||||
ADD,
|
||||
DROP,
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
EXCLUDE,
|
||||
ALTER
|
||||
}
|
||||
|
||||
private static class Column {
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.migration.AlterColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.Column;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* MySql specific DDL.
|
||||
@@ -113,10 +114,39 @@ public class MySqlDdl extends PlatformDdl {
|
||||
}
|
||||
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
|
||||
// alter comment currently not supported as it requires to repeat whole column definition
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Locks all tables for triggers that have to be updated.
|
||||
*/
|
||||
@Override
|
||||
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
if (!tables.isEmpty()) {
|
||||
buffer.append("lock tables ");
|
||||
int i = 0;
|
||||
for (String table : tables) {
|
||||
if (i > 0) {
|
||||
buffer.append(", ");
|
||||
}
|
||||
buffer.append(table).append(" write");
|
||||
i++;
|
||||
}
|
||||
buffer.endOfStatement();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks all tables for triggers that have to be updated.
|
||||
*/
|
||||
@Override
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
buffer.append("unlock tables").endOfStatement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-5
@@ -34,19 +34,19 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
@Override
|
||||
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.historyBuffer();
|
||||
recreateHistoryView(update);
|
||||
|
||||
DdlBuffer buffer = update.historyTriggerBuffer();
|
||||
String baseTable = update.getBaseTable();
|
||||
|
||||
buffer.append("lock tables ").append(baseTable).append(" write").endOfStatement();
|
||||
dropTriggers(buffer, baseTable);
|
||||
addBeforeUpdate(updateTriggerName(baseTable), update);
|
||||
addBeforeDelete(deleteTriggerName(baseTable), update);
|
||||
buffer.append("unlock tables").endOfStatement();
|
||||
}
|
||||
|
||||
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
DdlBuffer apply = update.historyTriggerBuffer();
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
|
||||
@@ -59,7 +59,7 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
DdlBuffer apply = update.historyTriggerBuffer();
|
||||
apply
|
||||
.append("delimiter $$").newLine()
|
||||
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
|
||||
|
||||
@@ -20,10 +20,12 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.IdentityType;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -442,13 +444,16 @@ public class PlatformDdl {
|
||||
.append(" ").append(addColumn).append(" ").append(column.getName())
|
||||
.append(" ").append(convertedType);
|
||||
|
||||
if (!onHistoryTable) {
|
||||
|
||||
if (defaultValue != null) {
|
||||
// Add default value also to history table if it is not excluded
|
||||
if (defaultValue != null) {
|
||||
if (!onHistoryTable || !isTrue(column.isHistoryExclude())) {
|
||||
buffer.append(" default ");
|
||||
buffer.append(defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!onHistoryTable) {
|
||||
if (isTrue(column.isNotnull())) {
|
||||
buffer.append(" not null");
|
||||
}
|
||||
@@ -456,7 +461,8 @@ public class PlatformDdl {
|
||||
|
||||
// check constraints cannot be added in one statement for h2
|
||||
if (!StringHelper.isNull(column.getCheckConstraint())) {
|
||||
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
|
||||
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
|
||||
column.getCheckConstraint());
|
||||
buffer.append(ddl).endOfStatement();
|
||||
}
|
||||
} else {
|
||||
@@ -641,4 +647,18 @@ public class PlatformDdl {
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mysql-specific: Locks all tables for triggers that have to be updated.
|
||||
*/
|
||||
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mysql-specific: Unlocks all tables for triggers that have to be updated.
|
||||
*/
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+15
-21
@@ -62,7 +62,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String procedureName = procedureName(baseTableName);
|
||||
String triggerName = triggerName(baseTableName);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryTrigger();
|
||||
apply
|
||||
.append("create trigger ").append(triggerName).newLine()
|
||||
.append(" before update or delete on ").append(baseTableName).newLine()
|
||||
@@ -79,13 +79,21 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
|
||||
apply
|
||||
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
|
||||
.append("begin").newLine();
|
||||
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine();
|
||||
|
||||
apply.append("declare").newLine()
|
||||
.append(" lowerTs timestamptz;").newLine()
|
||||
.append(" upperTs timestamptz;").newLine();
|
||||
|
||||
apply.append("begin").newLine()
|
||||
.append(" lowerTs = lower(OLD.sys_period);").newLine()
|
||||
.append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine();
|
||||
|
||||
apply
|
||||
.append(" if (TG_OP = 'UPDATE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine()
|
||||
.append(" return new;").newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
@@ -107,7 +115,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String historyTable = historyTableName(table.getName());
|
||||
|
||||
List<String> columnNames = columnNamesForApply(table);
|
||||
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
|
||||
createOrReplaceFunction(writer.applyHistoryTrigger(), procedureName, historyTable, columnNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,21 +124,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
String procedureName = procedureName(update.getBaseTable());
|
||||
|
||||
recreateHistoryView(update);
|
||||
createOrReplaceFunction(update.historyBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* For postgres we need to drop and recreate the view. Well, we could add columns to the end of the view
|
||||
* but otherwise we need to drop and create it.
|
||||
*/
|
||||
private void recreateHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.dropDependencyBuffer();
|
||||
// we need to drop the view early/first before any changes to the tables etc
|
||||
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
|
||||
|
||||
// recreate the view with specific columns specified (the columns generally are not dropped until later)
|
||||
createWithHistoryView(update);
|
||||
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,7 +132,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
|
||||
appendColumnNames(buffer, columns, "");
|
||||
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), ").append(currentTimestamp).append("), ");
|
||||
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
|
||||
appendColumnNames(buffer, columns, "OLD.");
|
||||
buffer.append(");").newLine();
|
||||
}
|
||||
|
||||
+23
-10
@@ -31,19 +31,24 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
enableSystemVersioning(writer, baseTable);
|
||||
}
|
||||
|
||||
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
apply.append("alter table ").append(baseTable).newLine()
|
||||
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
|
||||
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
private String getHistoryTable(String baseTable) {
|
||||
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
|
||||
// Setting SYSTEM_VERSIONING to ON failed because history table 'xxx_history' is not specified in two-part name format.
|
||||
if (historyTable.indexOf('.') == -1) {
|
||||
historyTable = "dbo." +historyTable; // so add the default schema, if none was specified.
|
||||
}
|
||||
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=").append(historyTable).append("))").endOfStatement();
|
||||
return historyTable;
|
||||
}
|
||||
|
||||
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
apply.append("alter table ").append(baseTable).newLine()
|
||||
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
|
||||
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
|
||||
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
|
||||
|
||||
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
|
||||
.append(getHistoryTable(baseTable)).append("))").endOfStatement();
|
||||
|
||||
DdlBuffer drop = writer.dropAll();
|
||||
drop.append("IF OBJECT_ID('").append(baseTable).append("', 'U') IS NOT NULL alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
|
||||
@@ -53,7 +58,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
@Override
|
||||
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
|
||||
String baseTable = dropHistoryTable.getBaseTable();
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
|
||||
// drop default constraints
|
||||
|
||||
@@ -76,7 +81,15 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
|
||||
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) throws IOException {
|
||||
// SQL Server 2016 does not need triggers
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
String baseTableName = baseTable.getBaseTable();
|
||||
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = off (history_table=")
|
||||
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
|
||||
apply.append("-- history migration goes here").newLine();
|
||||
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = on (history_table=")
|
||||
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,14 @@ public class MigrationXmlReader {
|
||||
*/
|
||||
public static Migration read(String resourcePath) {
|
||||
|
||||
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
|
||||
if (is == null) {
|
||||
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
|
||||
try (InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath)) {
|
||||
if (is == null) {
|
||||
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
|
||||
}
|
||||
return read(is);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to auto close resource " + resourcePath, e);
|
||||
}
|
||||
|
||||
return read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,6 @@ public class MigrationXmlWriter {
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
|
||||
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
|
||||
writer.write("<!DOCTYPE xml>\n");
|
||||
if (comment != null) {
|
||||
writer.write("<!-- ");
|
||||
writer.write(comment);
|
||||
|
||||
@@ -113,7 +113,8 @@ public class CurrentModel {
|
||||
}
|
||||
ddl.append(write.apply().getBuffer());
|
||||
ddl.append(write.applyForeignKeys().getBuffer());
|
||||
ddl.append(write.applyHistory().getBuffer());
|
||||
ddl.append(write.applyHistoryView().getBuffer());
|
||||
ddl.append(write.applyHistoryTrigger().getBuffer());
|
||||
|
||||
return ddl.toString();
|
||||
}
|
||||
|
||||
@@ -527,5 +527,8 @@ public class MColumn {
|
||||
if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
|
||||
fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
|
||||
}
|
||||
if (hasValue(alterColumn.isHistoryExclude())) {
|
||||
historyExclude = alterColumn.isHistoryExclude();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +621,7 @@ public class MTable {
|
||||
DropColumn dropColumn = new DropColumn();
|
||||
dropColumn.setTableName(name);
|
||||
dropColumn.setColumnName(existingColumn.getName());
|
||||
if (withHistory && !existingColumn.isHistoryExclude()) {
|
||||
if (withHistory) {
|
||||
// These dropColumns should occur on the history
|
||||
// table as well as the base table
|
||||
dropColumn.setWithHistory(Boolean.TRUE);
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebeaninternal.dbmigration.model;
|
||||
import io.ebeaninternal.dbmigration.migration.ChangeSet;
|
||||
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.DropTable;
|
||||
import io.ebeaninternal.dbmigration.migration.Migration;
|
||||
|
||||
@@ -207,10 +208,26 @@ public class PendingDrops {
|
||||
|
||||
} else if (pendingDrop instanceof DropTable && dropTableIn((DropTable) pendingDrop, appliedDrops)) {
|
||||
iterator.remove();
|
||||
|
||||
} else if (pendingDrop instanceof DropHistoryTable && dropHistoryTableIn((DropHistoryTable) pendingDrop, appliedDrops)) {
|
||||
iterator.remove();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the pendingDrop is contained in the appliedDrops.
|
||||
*/
|
||||
private boolean dropHistoryTableIn(DropHistoryTable pendingDrop, ChangeSet appliedDrops) {
|
||||
for (Object o : appliedDrops.getChangeSetChildren()) {
|
||||
if (o instanceof DropHistoryTable && sameHistoryTable(pendingDrop, (DropHistoryTable) o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the pendingDrop is contained in the appliedDrops.
|
||||
*/
|
||||
@@ -235,6 +252,13 @@ public class PendingDrops {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DropHistoryTable match by base-table name.
|
||||
*/
|
||||
private boolean sameHistoryTable(DropHistoryTable pendingDrop, DropHistoryTable o) {
|
||||
return pendingDrop.getBaseTable().equals(o.getBaseTable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DropTable match by table name.
|
||||
*/
|
||||
|
||||
@@ -97,7 +97,8 @@ public class PlatformDdlWriter {
|
||||
writer.append("-- apply changes\n");
|
||||
writer.append(write.apply().getBuffer());
|
||||
writer.append(write.applyForeignKeys().getBuffer());
|
||||
writer.append(write.applyHistory().getBuffer());
|
||||
writer.append(write.applyHistoryView().getBuffer());
|
||||
writer.append(write.applyHistoryTrigger().getBuffer());
|
||||
}
|
||||
|
||||
private void prependDropDependencies(Writer writer, DdlBuffer buffer) throws IOException {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+9
-29
@@ -1,18 +1,18 @@
|
||||
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;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.PropertyForeignKey;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
import io.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
|
||||
/**
|
||||
* Add the intersection table to the model.
|
||||
*/
|
||||
public class ModelBuildIntersectionTable {
|
||||
class ModelBuildIntersectionTable {
|
||||
|
||||
private final ModelBuildContext ctx;
|
||||
|
||||
@@ -22,9 +22,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,31 +49,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);
|
||||
|
||||
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);
|
||||
PropertyForeignKey foreignKey = manyProp.getForeignKey();
|
||||
if (foreignKey == null || !foreignKey.isNoConstraint()) {
|
||||
ctx.fkeyBuilder(intersectionTable)
|
||||
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
|
||||
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
|
||||
}
|
||||
intersectionTable.checkDuplicateForeignKeys();
|
||||
}
|
||||
|
||||
private MTable createTable() {
|
||||
|
||||
+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.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
@@ -87,12 +88,15 @@ public class ExtraDdlXmlReader {
|
||||
*/
|
||||
public static ExtraDdl read(String resourcePath) {
|
||||
|
||||
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
|
||||
if (is == null) {
|
||||
// we expect this and check for null
|
||||
return null;
|
||||
try (InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath)) {
|
||||
if (is == null) {
|
||||
// we expect this and check for null
|
||||
return null;
|
||||
}
|
||||
return read(is);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error on auto close of " + resourcePath, e);
|
||||
}
|
||||
return read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
|
||||
default:
|
||||
// not using autoTune when explicitly loading the l2 bean cache
|
||||
// or when using Versions query
|
||||
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -106,11 +107,14 @@ public class DefaultAutoTuneService implements AutoTuneService {
|
||||
loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
|
||||
} else {
|
||||
// look for autotune as a resource
|
||||
InputStream stream = getClass().getResourceAsStream("/" + tuningFile);
|
||||
if (stream != null) {
|
||||
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
|
||||
} else {
|
||||
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
|
||||
try (InputStream stream = getClass().getResourceAsStream("/" + tuningFile)) {
|
||||
if (stream != null) {
|
||||
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
|
||||
} else {
|
||||
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Error on auto close of " + tuningFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -15,20 +15,26 @@ public class CachedBeanDataToBean {
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (desc.getInheritInfo() != null) {
|
||||
desc = desc.getInheritInfo().readType(bean.getClass()).desc();
|
||||
}
|
||||
|
||||
if (idProperty != null) {
|
||||
// load the id property
|
||||
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -170,11 +170,7 @@ public abstract class AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
if (isLogSql()) {
|
||||
String logSql = TrimLogSql.trim(sql);
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
|
||||
}
|
||||
trans.logSql(logSql);
|
||||
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
|
||||
}
|
||||
|
||||
setResultSet(pstmt.executeQuery(), null);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ 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.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.dbmigration.DbOffline;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -126,13 +126,7 @@ public class DatabasePlatformFactory {
|
||||
throw new PersistenceException(ex);
|
||||
|
||||
} finally {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
logger.error(null, ex);
|
||||
}
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.ServerConfigProvider;
|
||||
import io.ebean.config.TenantMode;
|
||||
import io.ebean.config.UnderscoreNamingConvention;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.properties.PropertiesLoader;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiContainerBootup;
|
||||
@@ -63,9 +63,8 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
|
||||
private void invokeBootupPlugin() {
|
||||
Iterator<SpiContainerBootup> it = ServiceLoader.load(SpiContainerBootup.class).iterator();
|
||||
while (it.hasNext()) {
|
||||
it.next().bootup();
|
||||
for (SpiContainerBootup boot : ServiceLoader.load(SpiContainerBootup.class)) {
|
||||
boot.bootup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +103,13 @@ public class DefaultContainer implements SpiContainer {
|
||||
public SpiEbeanServer createServer(ServerConfig serverConfig) {
|
||||
|
||||
synchronized (this) {
|
||||
setNamingConvention(serverConfig);
|
||||
if (serverConfig.isDefaultServer()) {
|
||||
for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) {
|
||||
configProvider.apply(serverConfig);
|
||||
}
|
||||
}
|
||||
|
||||
setNamingConvention(serverConfig);
|
||||
BootupClasses bootupClasses = getBootupClasses(serverConfig);
|
||||
|
||||
boolean online = true;
|
||||
@@ -374,25 +378,14 @@ public class DefaultContainer implements SpiContainer {
|
||||
throw new RuntimeException("DataSource not set?");
|
||||
}
|
||||
|
||||
Connection c = null;
|
||||
try {
|
||||
c = serverConfig.getDataSource().getConnection();
|
||||
if (!serverConfig.isAutoCommitMode() && c.getAutoCommit()) {
|
||||
try (Connection connection = serverConfig.getDataSource().getConnection()) {
|
||||
if (!serverConfig.isAutoCommitMode() && connection.getAutoCommit()) {
|
||||
logger.warn("DataSource [{}] has autoCommit defaulting to true!", serverConfig.getName());
|
||||
}
|
||||
return true;
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
|
||||
} finally {
|
||||
if (c != null) {
|
||||
try {
|
||||
c.close();
|
||||
} catch (SQLException ex) {
|
||||
logger.error("Error closing connection", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,16 +8,20 @@ import io.ebean.DocumentStore;
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.ExtendedServer;
|
||||
import io.ebean.Filter;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
import io.ebean.FutureRowCount;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.MergeOptionsBuilder;
|
||||
import io.ebean.PagedList;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.SqlQuery;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.SqlUpdate;
|
||||
@@ -63,8 +67,11 @@ import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiJsonContext;
|
||||
import io.ebeaninternal.api.SpiLogManager;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQuery.Type;
|
||||
import io.ebeaninternal.api.SpiSqlQuery;
|
||||
import io.ebeaninternal.api.SpiSqlUpdate;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.api.SpiTransactionManager;
|
||||
import io.ebeaninternal.api.TransactionEventTable;
|
||||
@@ -112,10 +119,10 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -144,6 +151,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final DataTimeZone dataTimeZone;
|
||||
|
||||
/**
|
||||
* Clock to use for WhenModified and WhenCreated.
|
||||
*/
|
||||
private Clock clock;
|
||||
|
||||
private final CallStackFactory callStackFactory;
|
||||
|
||||
/**
|
||||
@@ -189,6 +201,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final CurrentTenantProvider currentTenantProvider;
|
||||
|
||||
private final SpiLogManager logManager;
|
||||
|
||||
/**
|
||||
* The default PersistenceContextScope used if it is not explicitly set on a query.
|
||||
*/
|
||||
@@ -229,6 +243,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
|
||||
|
||||
this.logManager = config.getLogManager();
|
||||
this.dtoBeanManager = config.getDtoBeanManager();
|
||||
this.serverConfig = config.getServerConfig();
|
||||
this.objectGraphStats = new ConcurrentHashMap<>();
|
||||
@@ -268,6 +283,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.beanLoader = new DefaultBeanLoader(this);
|
||||
this.jsonContext = config.createJsonContext(this);
|
||||
this.dataTimeZone = config.getDataTimeZone();
|
||||
this.clock = config.getServerConfig().getClock();
|
||||
|
||||
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
|
||||
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
|
||||
@@ -315,6 +331,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiLogManager log() {
|
||||
return logManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectQueryOrigins() {
|
||||
return collectQueryOrigins;
|
||||
@@ -487,6 +508,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return serverName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtendedServer extended() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clockNow() {
|
||||
return clock.millis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClock(Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanState getBeanState(Object bean) {
|
||||
if (bean instanceof EntityBean) {
|
||||
@@ -802,7 +838,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void commitTransaction() {
|
||||
transactionManager.scope().commit();
|
||||
currentTransaction().commit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -810,7 +846,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void rollbackTransaction() {
|
||||
transactionManager.scope().rollback();
|
||||
currentTransaction().rollback();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -843,7 +879,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public void endTransaction() {
|
||||
transactionManager.scope().end();
|
||||
Transaction transaction = transactionManager.getInScope();
|
||||
if (transaction != null) {
|
||||
transaction.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -916,6 +955,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new DefaultUpdateQuery<>(createQuery(beanType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean) {
|
||||
merge(bean, MergeOptionsBuilder.defaultOptions(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean, MergeOptions options) {
|
||||
merge(bean, options, null);
|
||||
@@ -966,7 +1010,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;
|
||||
@@ -999,6 +1043,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) {
|
||||
|
||||
@@ -1146,7 +1200,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
|
||||
spiQuery.setType(Type.BEAN);
|
||||
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
|
||||
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
|
||||
// See if we can skip doing the fetch completely by getting the bean from the
|
||||
// persistence context or the bean cache
|
||||
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
|
||||
@@ -1265,8 +1319,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
@Override
|
||||
public <T> int findCount(Query<T> query, Transaction t) {
|
||||
|
||||
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
|
||||
return findCountWithCopy(copy, t);
|
||||
SpiQuery<T> spiQuery = ((SpiQuery<T>) query);
|
||||
if (!spiQuery.isDistinct()) {
|
||||
spiQuery = spiQuery.copy();
|
||||
}
|
||||
return findCountWithCopy(spiQuery, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1553,6 +1610,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
private <P> P executeSqlQuery(Function<RelationalQueryRequest, P> fun, SpiSqlQuery query) {
|
||||
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
return fun.apply(request);
|
||||
} finally {
|
||||
request.endTransIfRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEachRow(SpiSqlQuery query, RowConsumer consumer) {
|
||||
executeSqlQuery((req) -> req.findEachRow(consumer), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper) {
|
||||
return executeSqlQuery((req) -> req.findListMapper(mapper), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper) {
|
||||
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
|
||||
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls) {
|
||||
return executeSqlQuery((req) -> req.findSingleAttribute(cls), query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
|
||||
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
|
||||
@@ -1664,7 +1756,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
if (beans == null || beans.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeInTrans((txn) -> {
|
||||
for (Object bean : beans) {
|
||||
update(checkEntityBean(bean), txn);
|
||||
@@ -1706,7 +1797,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
if (beans == null || beans.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
executeInTrans((txn) -> {
|
||||
for (Object bean : beans) {
|
||||
persister.insert(checkEntityBean(bean), txn);
|
||||
@@ -1775,24 +1865,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException {
|
||||
return saveAllInternal(beans.iterator(), transaction);
|
||||
return saveAllInternal(beans, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int saveAll(Collection<?> beans) throws OptimisticLockException {
|
||||
return saveAllInternal(beans.iterator(), null);
|
||||
return saveAllInternal(beans, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all beans in the iterator with an explicit transaction.
|
||||
*/
|
||||
private int saveAllInternal(Iterator<?> it, Transaction transaction) {
|
||||
private int saveAllInternal(Collection<?> beans, Transaction transaction) {
|
||||
|
||||
if (beans == null || beans.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return executeInTrans((txn) -> {
|
||||
txn.checkBatchEscalationOnCollection();
|
||||
int saveCount = 0;
|
||||
while (it.hasNext()) {
|
||||
persister.save(checkEntityBean(it.next()), txn);
|
||||
for (Object bean : beans) {
|
||||
persister.save(checkEntityBean(bean), txn);
|
||||
saveCount++;
|
||||
}
|
||||
|
||||
@@ -1880,12 +1973,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public int deleteAllPermanent(Collection<?> beans) {
|
||||
return deleteAllInternal(beans.iterator(), null, true);
|
||||
return deleteAllInternal(beans, null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteAllPermanent(Collection<?> beans, Transaction t) {
|
||||
return deleteAllInternal(beans.iterator(), t, true);
|
||||
return deleteAllInternal(beans, t, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1893,7 +1986,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public int deleteAll(Collection<?> beans) {
|
||||
return deleteAllInternal(beans.iterator(), null, false);
|
||||
return deleteAllInternal(beans, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1901,21 +1994,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public int deleteAll(Collection<?> beans, Transaction t) {
|
||||
return deleteAllInternal(beans.iterator(), t, false);
|
||||
return deleteAllInternal(beans, t, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the iterator with an explicit transaction.
|
||||
*/
|
||||
private int deleteAllInternal(Iterator<?> it, Transaction transaction, boolean permanent) {
|
||||
private int deleteAllInternal(Collection<?> beans, Transaction transaction, boolean permanent) {
|
||||
|
||||
if (beans == null || beans.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return executeInTrans((txn) -> {
|
||||
|
||||
txn.checkBatchEscalationOnCollection();
|
||||
int deleteCount = 0;
|
||||
while (it.hasNext()) {
|
||||
EntityBean bean = checkEntityBean(it.next());
|
||||
persister.delete(bean, txn, permanent);
|
||||
for (Object bean : beans) {
|
||||
persister.delete(checkEntityBean(bean), txn, permanent);
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
@@ -1948,6 +2043,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return persister.executeSqlUpdate(updSql, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
|
||||
persister.addBatch(sqlUpdate, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
|
||||
return persister.executeBatch(sqlUpdate, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the updateSql.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Update;
|
||||
import io.ebeaninternal.api.BindParams;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiSqlUpdate;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
|
||||
private static final long serialVersionUID = -6493829438421253102L;
|
||||
|
||||
private transient final EbeanServer server;
|
||||
private transient final SpiEbeanServer server;
|
||||
|
||||
/**
|
||||
* The parameters used to bind to the sql.
|
||||
@@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
|
||||
private Object generatedKey;
|
||||
|
||||
/**
|
||||
* Set when batching explicitly used.
|
||||
*/
|
||||
private boolean batched;
|
||||
|
||||
/**
|
||||
* Transaction used for addBatch() executeBatch() processing.
|
||||
*/
|
||||
private transient SpiTransaction transaction;
|
||||
|
||||
/**
|
||||
* Create with server sql and bindParams object.
|
||||
* <p>
|
||||
@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
* same time.
|
||||
* </p>
|
||||
*/
|
||||
public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) {
|
||||
public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) {
|
||||
this.server = server;
|
||||
this.sql = sql;
|
||||
this.bindParams = bindParams;
|
||||
@@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
* Create with a specific server. This means you can use the
|
||||
* SqlUpdate.execute() method.
|
||||
*/
|
||||
public DefaultSqlUpdate(EbeanServer server, String sql) {
|
||||
public DefaultSqlUpdate(SpiEbeanServer server, String sql) {
|
||||
this(server, sql, new BindParams());
|
||||
}
|
||||
|
||||
@@ -97,6 +108,11 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
this(null, sql, new BindParams());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
addPos = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object executeGetKey() {
|
||||
execute();
|
||||
@@ -106,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
@Override
|
||||
public int execute() {
|
||||
if (server != null) {
|
||||
if (batched) {
|
||||
server.executeBatch(this, transaction);
|
||||
return -1;
|
||||
}
|
||||
return server.execute(this);
|
||||
} else {
|
||||
// Hopefully this doesn't catch anyone out...
|
||||
@@ -113,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch() {
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("No EbeanServer set?");
|
||||
}
|
||||
if (!batched) {
|
||||
throw new IllegalStateException("No prior addBatch() called?");
|
||||
}
|
||||
return server.executeBatch(this, transaction);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addBatch() {
|
||||
if (server == null) {
|
||||
throw new IllegalStateException("No EbeanServer set?");
|
||||
}
|
||||
if (transaction == null) {
|
||||
transaction = server.currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()");
|
||||
}
|
||||
}
|
||||
|
||||
batched = true;
|
||||
server.addBatch(this, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getGeneratedKey() {
|
||||
return generatedKey;
|
||||
@@ -182,8 +230,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,111 @@
|
||||
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();
|
||||
if (serverConfig.getClassLoadConfig().isJavaxJAXBPresent()) {
|
||||
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() {
|
||||
if (xmlEbeanList != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,9 @@ import io.ebean.plugin.SpiServer;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiJsonContext;
|
||||
import io.ebeaninternal.api.SpiLogManager;
|
||||
import io.ebeaninternal.api.SpiLogger;
|
||||
import io.ebeaninternal.api.SpiLoggerFactory;
|
||||
import io.ebeaninternal.api.SpiProfileHandler;
|
||||
import io.ebeaninternal.dbmigration.DbOffline;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneService;
|
||||
@@ -42,6 +45,10 @@ 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.logger.DLogManager;
|
||||
import io.ebeaninternal.server.logger.DLoggerFactory;
|
||||
import io.ebeaninternal.server.persist.Binder;
|
||||
import io.ebeaninternal.server.persist.DefaultPersister;
|
||||
import io.ebeaninternal.server.persist.platform.MultiValueBind;
|
||||
@@ -132,16 +139,19 @@ public class InternalConfiguration {
|
||||
|
||||
private final MultiValueBind multiValueBind;
|
||||
|
||||
private final SpiLogManager logManager;
|
||||
|
||||
public InternalConfiguration(ClusterManager clusterManager,
|
||||
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
|
||||
ServerConfig serverConfig, BootupClasses bootupClasses) {
|
||||
|
||||
this.serverConfig = serverConfig;
|
||||
this.logManager = initLogManager();
|
||||
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
|
||||
this.jsonFactory = serverConfig.getJsonFactory();
|
||||
this.clusterManager = clusterManager;
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.cacheManager = cacheManager;
|
||||
this.serverConfig = serverConfig;
|
||||
this.bootupClasses = bootupClasses;
|
||||
|
||||
this.databasePlatform = serverConfig.getDatabasePlatform();
|
||||
@@ -153,10 +163,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();
|
||||
|
||||
@@ -165,6 +177,20 @@ public class InternalConfiguration {
|
||||
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
|
||||
}
|
||||
|
||||
private SpiLogManager initLogManager() {
|
||||
|
||||
// allow plugin - i.e. capture executed SQL for testing/asserts
|
||||
SpiLoggerFactory loggerFactory = serverConfig.service(SpiLoggerFactory.class);
|
||||
if (loggerFactory == null) {
|
||||
loggerFactory = new DLoggerFactory();
|
||||
}
|
||||
|
||||
SpiLogger sql = loggerFactory.create("io.ebean.SQL");
|
||||
SpiLogger sum = loggerFactory.create("io.ebean.SUM");
|
||||
SpiLogger txn = loggerFactory.create("io.ebean.TXN");
|
||||
return new DLogManager(sql, sum, txn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return the ExpressionFactory based on configuration and database platform.
|
||||
*/
|
||||
@@ -258,27 +284,16 @@ public class InternalConfiguration {
|
||||
|
||||
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
|
||||
if (historySupport == null) {
|
||||
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
|
||||
return new Binder(typeManager, logManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
|
||||
}
|
||||
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
|
||||
return new Binder(typeManager, logManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -385,7 +400,7 @@ public class InternalConfiguration {
|
||||
|
||||
TransactionManagerOptions options =
|
||||
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager);
|
||||
|
||||
if (serverConfig.isExplicitTransactionBeginMode()) {
|
||||
return new ExplicitTransactionManager(options);
|
||||
@@ -518,4 +533,8 @@ public class InternalConfiguration {
|
||||
public DtoBeanManager getDtoBeanManager() {
|
||||
return dtoBeanManager;
|
||||
}
|
||||
|
||||
public SpiLogManager getLogManager() {
|
||||
return logManager;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
|
||||
private List<T> cacheBeans;
|
||||
|
||||
private BeanPropertyAssocMany<?> manyProperty;
|
||||
|
||||
private boolean inlineCountDistinct;
|
||||
|
||||
/**
|
||||
* Create the InternalQueryRequest.
|
||||
*/
|
||||
@@ -496,11 +500,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -733,4 +744,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
|
||||
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
|
||||
}
|
||||
|
||||
public void setInlineCountDistinct() {
|
||||
inlineCountDistinct = true;
|
||||
}
|
||||
|
||||
public boolean isInlineCountDistinct() {
|
||||
return inlineCountDistinct;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
|
||||
INSERT(EVT_INSERT),
|
||||
UPDATE(EVT_UPDATE),
|
||||
DELETE(EVT_DELETE),
|
||||
SOFT_DELETE(EVT_SOFT_DELETE),
|
||||
DELETE_SOFT(EVT_DELETE_SOFT),
|
||||
DELETE_PERMANENT(EVT_DELETE_PERMANENT),
|
||||
UPDATESQL(EVT_UPDATESQL),
|
||||
CALLABLESQL(EVT_CALLABLESQL);
|
||||
|
||||
@@ -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.DeleteMode;
|
||||
import io.ebeaninternal.server.persist.Flags;
|
||||
import io.ebeaninternal.server.persist.PersistExecute;
|
||||
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
|
||||
@@ -35,6 +36,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;
|
||||
@@ -107,6 +109,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).
|
||||
*/
|
||||
@@ -154,6 +161,11 @@ 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, int flags) {
|
||||
|
||||
@@ -260,7 +272,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
onUpdateGeneratedProperties();
|
||||
}
|
||||
break;
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
onUpdateGeneratedProperties();
|
||||
break;
|
||||
}
|
||||
@@ -337,11 +349,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;
|
||||
}
|
||||
@@ -439,7 +456,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanDescriptor.cacheHandleUpdate(idValue, this, changeSet);
|
||||
break;
|
||||
case DELETE:
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
|
||||
break;
|
||||
default:
|
||||
@@ -459,7 +476,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanDescriptor.docStoreInsert(idValue, this, txn);
|
||||
break;
|
||||
case UPDATE:
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
beanDescriptor.docStoreUpdate(idValue, this, txn);
|
||||
break;
|
||||
case DELETE:
|
||||
@@ -480,7 +497,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
|
||||
break;
|
||||
case UPDATE:
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
|
||||
break;
|
||||
case DELETE:
|
||||
@@ -511,7 +528,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanPersistListener.deleted(bean);
|
||||
break;
|
||||
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
beanPersistListener.softDeleted(bean);
|
||||
break;
|
||||
|
||||
@@ -756,7 +773,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
executeUpdate();
|
||||
return -1;
|
||||
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
prepareForSoftDelete();
|
||||
executeSoftDelete();
|
||||
return -1;
|
||||
@@ -839,7 +856,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
switch (type) {
|
||||
case DELETE:
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
postDelete();
|
||||
break;
|
||||
case UPDATE:
|
||||
@@ -858,6 +875,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.
|
||||
*/
|
||||
@@ -905,15 +932,44 @@ 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:
|
||||
case DELETE_SOFT:
|
||||
controller.postSoftDelete(this);
|
||||
break;
|
||||
case DELETE:
|
||||
@@ -938,7 +994,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
case DELETE:
|
||||
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]" + draft);
|
||||
break;
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
transaction.logSummary("SoftDelete [" + name + "] [" + idValue + "]" + draft);
|
||||
break;
|
||||
default:
|
||||
@@ -1031,13 +1087,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1135,10 +1185,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a soft delete request.
|
||||
* Return the delete mode - Soft or Hard.
|
||||
*/
|
||||
public boolean isSoftDelete() {
|
||||
return Type.SOFT_DELETE == type;
|
||||
public DeleteMode deleteMode() {
|
||||
return Type.DELETE_SOFT == type ? DeleteMode.SOFT : DeleteMode.HARD;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1225,7 +1275,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
public long now() {
|
||||
if (now == 0) {
|
||||
now = System.currentTimeMillis();
|
||||
now = ebeanServer.clockNow();
|
||||
}
|
||||
return now;
|
||||
}
|
||||
@@ -1265,4 +1315,50 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiSqlUpdate;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
@@ -27,15 +26,18 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
|
||||
private String description;
|
||||
|
||||
private boolean addBatch;
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate updateSql,
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
|
||||
super(server, t, persistExecute, updateSql.getLabel());
|
||||
super(server, t, persistExecute, sqlUpdate.getLabel());
|
||||
this.type = Type.UPDATESQL;
|
||||
this.updateSql = (SpiSqlUpdate) updateSql;
|
||||
this.updateSql = sqlUpdate;
|
||||
updateSql.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,11 +45,24 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this statement to JDBC batch for later execution.
|
||||
*/
|
||||
public int addBatch() {
|
||||
this.addBatch = true;
|
||||
return executeOrQueue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow() {
|
||||
return persistExecute.executeSqlUpdate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest() {
|
||||
return addBatch || super.isBatchThisRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
return executeStatement();
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.ebean.Transaction;
|
||||
import io.ebean.Update;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.api.SpiSqlUpdate;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
@@ -91,4 +92,14 @@ public interface Persister {
|
||||
* Visit the metrics.
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Add the statement to JDBC batch for later execution via executeBatch.
|
||||
*/
|
||||
void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the associated batched statement.
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
@@ -30,6 +32,31 @@ public interface RelationalQueryEngine {
|
||||
*/
|
||||
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
|
||||
|
||||
/**
|
||||
* Find single attribute.
|
||||
*/
|
||||
<T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls);
|
||||
|
||||
/**
|
||||
* Find single attribute list.
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
|
||||
|
||||
/**
|
||||
* Find one via mapper.
|
||||
*/
|
||||
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Find list via mapper.
|
||||
*/
|
||||
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Find each via raw consumer.
|
||||
*/
|
||||
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
|
||||
|
||||
/**
|
||||
* Collect SQL query execution statistics.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.SqlQuery;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.Transaction;
|
||||
@@ -53,6 +55,27 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
}
|
||||
}
|
||||
|
||||
boolean findEachRow(RowConsumer mapper) {
|
||||
queryEngine.findEachRow(this, mapper);
|
||||
return true;
|
||||
}
|
||||
|
||||
<T> List<T> findListMapper(RowMapper<T> mapper) {
|
||||
return queryEngine.findListMapper(this, mapper);
|
||||
}
|
||||
|
||||
<T> T findOneMapper(RowMapper<T> mapper) {
|
||||
return queryEngine.findOneMapper(this, mapper);
|
||||
}
|
||||
|
||||
public <T> List<T> findSingleAttributeList(Class<T> cls) {
|
||||
return queryEngine.findSingleAttributeList(this, cls);
|
||||
}
|
||||
|
||||
public <T> T findSingleAttribute(Class<T> cls) {
|
||||
return queryEngine.findSingleAttribute(this, cls);
|
||||
}
|
||||
|
||||
public void findEach(Consumer<SqlRow> consumer) {
|
||||
queryEngine.findEach(this, consumer);
|
||||
}
|
||||
@@ -104,4 +127,35 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
}
|
||||
}
|
||||
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
public void incrementRows() {
|
||||
rows++;
|
||||
}
|
||||
|
||||
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
|
||||
|
||||
List<T> list = new ArrayList<>();
|
||||
while (next()) {
|
||||
list.add(mapper.map(resultSet, rows++));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
|
||||
if (!next()) {
|
||||
return null;
|
||||
} else {
|
||||
return mapper.map(resultSet, rows++);
|
||||
}
|
||||
}
|
||||
|
||||
public void mapEach(RowConsumer consumer) throws SQLException {
|
||||
while (next()) {
|
||||
consumer.accept(resultSet, rows++);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to io.ebean.event.ServletContextListener.
|
||||
* <p>
|
||||
* Listens for webserver server starting and stopping events.
|
||||
* <p>
|
||||
* Register this listener in the web.xml configuration file. This will listen
|
||||
* for startup and shutdown events.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
public class ServletContextListener implements javax.servlet.ServletContextListener {
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,11 +59,8 @@ class ManifestReader {
|
||||
try {
|
||||
Enumeration<URL> resources = classLoader.getResources(resourcePath);
|
||||
while (resources.hasMoreElements()) {
|
||||
InputStream is = resources.nextElement().openStream();
|
||||
try {
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
read(new Manifest(is));
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -72,14 +69,6 @@ class ManifestReader {
|
||||
return packageSet;
|
||||
}
|
||||
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("Error closing manifest InputStream", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the entity packages from the manifest.
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,47 @@
|
||||
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) {
|
||||
@@ -14,4 +50,18 @@ abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
|
||||
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,7 +7,8 @@ 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;
|
||||
@@ -15,7 +16,7 @@ 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -41,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;
|
||||
@@ -60,13 +62,19 @@ import io.ebeaninternal.server.el.ElComparatorProperty;
|
||||
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.DeleteMode;
|
||||
import io.ebeaninternal.server.persist.DmlUtil;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
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.DefaultOrmQuery;
|
||||
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 +108,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 +122,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;
|
||||
|
||||
@@ -145,6 +153,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
*/
|
||||
private final IdType idType;
|
||||
|
||||
/**
|
||||
* Set when Id property is marked with GeneratedValue annotation.
|
||||
*/
|
||||
private final boolean idGeneratedValue;
|
||||
|
||||
private final boolean idTypePlatformDefault;
|
||||
|
||||
private final PlatformIdGenerator idGenerator;
|
||||
@@ -229,7 +242,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 +252,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 +413,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 +435,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);
|
||||
@@ -443,6 +456,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
this.defaultSelectClause = deploy.getDefaultSelectClause();
|
||||
this.idType = deploy.getIdType();
|
||||
this.idGeneratedValue = deploy.isIdGeneratedValue();
|
||||
this.idTypePlatformDefault = deploy.isIdTypePlatformDefault();
|
||||
this.idGenerator = deploy.getIdGenerator();
|
||||
this.sequenceName = deploy.getSequenceName();
|
||||
@@ -601,7 +615,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;
|
||||
}
|
||||
@@ -624,9 +638,9 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
*/
|
||||
public void setEbeanServer(SpiEbeanServer ebeanServer) {
|
||||
this.ebeanServer = ebeanServer;
|
||||
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
|
||||
for (BeanPropertyAssocMany<?> assocMany : propertiesMany) {
|
||||
// used for creating lazy loading lists etc
|
||||
aPropertiesMany.setLoader(ebeanServer);
|
||||
assocMany.setEbeanServer(ebeanServer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,27 +670,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 +702,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 +729,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 +738,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 +788,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 +810,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 +872,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.
|
||||
*/
|
||||
@@ -887,7 +908,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
case INSERT:
|
||||
return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null;
|
||||
case UPDATE:
|
||||
case SOFT_DELETE:
|
||||
case DELETE_SOFT:
|
||||
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request) : null;
|
||||
case DELETE:
|
||||
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) : null;
|
||||
@@ -936,7 +957,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 +971,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.
|
||||
*/
|
||||
@@ -967,11 +992,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
jsonWriter.writeEndObject();
|
||||
}
|
||||
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList, DeleteMode mode) {
|
||||
if (id != null) {
|
||||
return deleteById(id, softDelete);
|
||||
return deleteById(id, mode);
|
||||
} else {
|
||||
return deleteByIdList(idList, softDelete);
|
||||
return deleteByIdList(idList, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -993,9 +1018,9 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* Return SQL that can be used to delete a list of Id's without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
|
||||
private SqlUpdate deleteByIdList(List<Object> idList, DeleteMode mode) {
|
||||
|
||||
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
|
||||
String baseSql = mode.isHard() ? deleteByIdInSql : softDeleteByIdInSql;
|
||||
StringBuilder sb = new StringBuilder(baseSql);
|
||||
String inClause = idBinder.getIdInValueExprDelete(idList.size());
|
||||
sb.append(inClause);
|
||||
@@ -1009,14 +1034,14 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* Return SQL that can be used to delete by Id without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteById(Object id, boolean softDelete) {
|
||||
private SqlUpdate deleteById(Object id, DeleteMode mode) {
|
||||
|
||||
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
|
||||
String baseSql = mode.isHard() ? deleteByIdSql : softDeleteByIdSql;
|
||||
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
|
||||
|
||||
Object[] bindValues = idBinder.getBindValues(id);
|
||||
for (Object bindValue : bindValues) {
|
||||
sqlDelete.addParameter(bindValue);
|
||||
sqlDelete.setNextParameter(bindValue);
|
||||
}
|
||||
|
||||
return sqlDelete;
|
||||
@@ -1421,7 +1446,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1639,6 +1664,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);
|
||||
@@ -1705,7 +1731,15 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* associated L2 bean caching.
|
||||
*/
|
||||
public boolean isDeleteByStatement() {
|
||||
return deleteRecurseSkippable && !isBeanCaching();
|
||||
return persistListener == null
|
||||
&& persistController == null
|
||||
&& deleteRecurseSkippable && !isBeanCaching();
|
||||
}
|
||||
|
||||
public boolean isDeleteByBulk() {
|
||||
return persistListener == null
|
||||
&& persistController == null
|
||||
&& propertiesManyToMany.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1787,6 +1821,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.
|
||||
*/
|
||||
@@ -1847,6 +1886,9 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
* on first access (lazy load) or immediately (eager load)
|
||||
*/
|
||||
public EntityBean createEntityBean(boolean isNew) {
|
||||
if (prototypeEntityBean == null) {
|
||||
throw new UnsupportedOperationException("cannot create entity bean for abstract entity " + getName());
|
||||
}
|
||||
try {
|
||||
EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance();
|
||||
|
||||
@@ -1874,12 +1916,33 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* We actually need to do a query because we don't know the type without the discriminator
|
||||
* value, just select the id property and discriminator column (auto added)
|
||||
*/
|
||||
private T findReferenceBean(Object id, PersistenceContext pc) {
|
||||
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
|
||||
query.setPersistenceContext(pc);
|
||||
return query
|
||||
// .select(getIdProperty().getName())
|
||||
// we do not select the id because we
|
||||
// probably have to load the entire bean
|
||||
.setId(id).findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference bean based on the id.
|
||||
*/
|
||||
@@ -1899,6 +1962,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (inheritInfo != null && !inheritInfo.isConcrete()) {
|
||||
return findReferenceBean(id, pc);
|
||||
}
|
||||
|
||||
EntityBean eb = createEntityBean();
|
||||
id = convertSetId(id, eb);
|
||||
|
||||
@@ -1931,6 +1998,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
public T createReference(Object id, PersistenceContext pc) {
|
||||
|
||||
try {
|
||||
if (inheritInfo != null && !inheritInfo.isConcrete()) {
|
||||
return findReferenceBean(id, pc);
|
||||
}
|
||||
|
||||
EntityBean eb = createEntityBean();
|
||||
id = convertSetId(id, eb);
|
||||
EntityBeanIntercept ebi = eb._ebean_getIntercept();
|
||||
@@ -1951,7 +2022,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) {
|
||||
|
||||
@@ -1983,7 +2055,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];
|
||||
@@ -2224,7 +2296,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
@Override
|
||||
public Property getProperty(String propName) {
|
||||
return findBeanProperty(propName);
|
||||
return findProperty(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2458,17 +2530,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);
|
||||
}
|
||||
@@ -2482,7 +2555,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
|
||||
@@ -2540,6 +2614,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.
|
||||
@@ -2809,6 +2887,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);
|
||||
@@ -2893,6 +3006,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the Id value is marked as a <code>@GeneratedValue</code>.
|
||||
*/
|
||||
public boolean isIdGeneratedValue() {
|
||||
return idGeneratedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the identity is the platform default (not explicitly set).
|
||||
*/
|
||||
@@ -2933,16 +3053,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();
|
||||
@@ -3131,6 +3241,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.
|
||||
@@ -3273,6 +3403,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);
|
||||
}
|
||||
@@ -3285,11 +3427,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +447,21 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
void beanPutAll(Collection<EntityBean> beans) {
|
||||
if (desc.inheritInfo != null) {
|
||||
Class<?> aClass = theClassOf(beans);
|
||||
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
|
||||
// check if all beans have the same class
|
||||
for (EntityBean bean : beans) {
|
||||
if (!bean.getClass().equals(aClass)) {
|
||||
aClass = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (aClass == null) {
|
||||
// there are different bean types in the collection, so we add one by one to the cache
|
||||
for (EntityBean bean : beans) {
|
||||
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
|
||||
}
|
||||
} else {
|
||||
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
|
||||
}
|
||||
} else {
|
||||
beanCachePutAllDirect(beans);
|
||||
}
|
||||
@@ -730,38 +770,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);
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user