mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
128
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
448d94a335 | ||
|
|
961a7ef7ea | ||
|
|
5cef1ad017 | ||
|
|
b814ca6e81 | ||
|
|
a24e6b6064 | ||
|
|
31d7c6fed8 | ||
|
|
c5fb1dbe23 | ||
|
|
b7770a9617 | ||
|
|
41abf34ef6 | ||
|
|
89beaee0f6 | ||
|
|
5052afbd07 | ||
|
|
d89a58482c | ||
|
|
eb1bca805e | ||
|
|
9cb66b112f | ||
|
|
210901ee2d | ||
|
|
396fb49acd | ||
|
|
a83896f1ff | ||
|
|
3483372ae3 | ||
|
|
4e1f817ade | ||
|
|
d4139aa8ef | ||
|
|
a6cfed7c44 | ||
|
|
cc17ea8911 | ||
|
|
141f0a8b16 | ||
|
|
9dbd903a73 | ||
|
|
b8105190d1 | ||
|
|
ef3d911e19 | ||
|
|
31fd0b7ed5 | ||
|
|
26d1288757 | ||
|
|
1d49e5b341 | ||
|
|
4a0806e453 | ||
|
|
ab48f796c6 | ||
|
|
0cb8bb0415 | ||
|
|
471691c747 | ||
|
|
c86e6b3cf9 | ||
|
|
b399713a85 | ||
|
|
f8d4ad7f33 | ||
|
|
7984a9ccd1 | ||
|
|
27cda09523 | ||
|
|
20fc23c19c | ||
|
|
bac53a5d75 | ||
|
|
2e0690efa9 | ||
|
|
fdd9b1905c | ||
|
|
fc966618a6 | ||
|
|
669ba7837a | ||
|
|
0edf90056c | ||
|
|
6b27a9d280 | ||
|
|
2b0669c2ae | ||
|
|
9fc10e8e04 | ||
|
|
fb5eb0cdd2 | ||
|
|
3351882915 | ||
|
|
31c6d16e73 | ||
|
|
cd8c12a9a8 | ||
|
|
3207ae1f08 | ||
|
|
5fe532c393 | ||
|
|
93b9cbf9ac | ||
|
|
8a34d6050b | ||
|
|
ab3e6ccbf7 | ||
|
|
cc58fc8195 | ||
|
|
1c34c09f3c | ||
|
|
8544712448 | ||
|
|
5e22079366 | ||
|
|
903b0a3929 | ||
|
|
b18bc82b8a | ||
|
|
50de32af29 | ||
|
|
0b18189e62 | ||
|
|
0b6e73d729 | ||
|
|
3108076953 | ||
|
|
7ed4435ccc | ||
|
|
7aef4ff1bd | ||
|
|
5bcad17ae1 | ||
|
|
b1beb0b787 | ||
|
|
585eb2b57b | ||
|
|
2653ec50aa | ||
|
|
cb4432a588 | ||
|
|
4d93b70bf9 | ||
|
|
0b299edf0d | ||
|
|
cf10246783 | ||
|
|
babba914a0 | ||
|
|
0397140e0b | ||
|
|
82def2710e | ||
|
|
60ad417e1b | ||
|
|
3af1b574fd | ||
|
|
60c8683914 | ||
|
|
72263c0af0 | ||
|
|
f66316138d | ||
|
|
b403942491 | ||
|
|
baa3909c63 | ||
|
|
81f42f8e95 | ||
|
|
0377e3ec5c | ||
|
|
58f6c67d78 | ||
|
|
84f7fc5d81 | ||
|
|
ea02bebc6f | ||
|
|
da874d84a8 | ||
|
|
049cacad1c | ||
|
|
ced8c79458 | ||
|
|
a66edf8077 | ||
|
|
94591954b3 | ||
|
|
e611353163 | ||
|
|
e7811910e9 | ||
|
|
138b587ff7 | ||
|
|
8fc6c4c648 | ||
|
|
ebdc28e1cd | ||
|
|
51f79e769c | ||
|
|
c2a69714ca | ||
|
|
f3213e7b90 | ||
|
|
55de997297 | ||
|
|
65890f9777 | ||
|
|
41c1e7260c | ||
|
|
7d35789db4 | ||
|
|
d266d6d440 | ||
|
|
6d5374e66a | ||
|
|
8410de8831 | ||
|
|
4b0230e862 | ||
|
|
fa96f81f8c | ||
|
|
a5fea5e9be | ||
|
|
bafba0babf | ||
|
|
e4088c5fbe | ||
|
|
0405ceb96c | ||
|
|
ebf96c87bf | ||
|
|
fcaf11f262 | ||
|
|
05df47c0d7 | ||
|
|
a8892292fb | ||
|
|
e876ef8d87 | ||
|
|
315b39813b | ||
|
|
6d5e0c4766 | ||
|
|
a2ce17aec0 | ||
|
|
fcffa92c55 | ||
|
|
7fda8079c2 |
@@ -1,8 +1,11 @@
|
||||
[](https://travis-ci.org/ebean-orm/ebean)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
|
||||
|
||||
# Need help?
|
||||
Post questions or issues to the Ebean google group - https://groups.google.com/forum/#!forum/ebean
|
||||
|
||||
# Documentation
|
||||
Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
|
||||
Goto [https://ebean.io/docs/](https://ebean.io/docs/)
|
||||
|
||||
|
||||
## Maven cental links:
|
||||
@@ -10,8 +13,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/io.ebean/ebean) - ebean
|
||||
## Other versions
|
||||
* [](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,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.28.1</version>
|
||||
<version>11.33.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.28.1</tag>
|
||||
<tag>ebean-11.33.3</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -117,13 +117,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>4.3</version>
|
||||
<version>4.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-datasource</artifactId>
|
||||
<version>4.3.2</version>
|
||||
<version>4.3.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -135,13 +135,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.11.1</version>
|
||||
<version>11.14.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.7.1</version>
|
||||
<version>4.7.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson core used internally by Ebean -->
|
||||
@@ -229,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.25.1</version>
|
||||
<version>11.27.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.196</version>
|
||||
<version>1.4.197</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.25.1</version>
|
||||
<version>11.27.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -37,4 +39,21 @@ public interface BackgroundExecutor {
|
||||
* </p>
|
||||
*/
|
||||
void executePeriodically(Runnable r, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Schedules a Runnable for one-shot action that becomes enabled after the given delay.
|
||||
*
|
||||
* @return a ScheduledFuture representing pending completion of the task and
|
||||
* whose get() method will return null upon completion
|
||||
*/
|
||||
ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Schedules a Callable for one-shot action that becomes enabled after the given delay.
|
||||
*
|
||||
* @return a ScheduledFuture that can be used to extract result or cancel
|
||||
*/
|
||||
<V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package io.ebean;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provides access to the internal state of an entity bean.
|
||||
*/
|
||||
@@ -111,4 +113,10 @@ public interface BeanState {
|
||||
* Reset the bean putting it into NEW state such that a save() results in an insert.
|
||||
*/
|
||||
void resetForInsert();
|
||||
|
||||
/**
|
||||
* Returns a map with load erros.
|
||||
*/
|
||||
@Nullable
|
||||
Map<String, Exception> getLoadErrors();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.function.Predicate;
|
||||
* String sql = "select id, name from customer where name like :name and status_code = :status";
|
||||
*
|
||||
* List<CustomerDto> beans =
|
||||
* Ebean.findDto(CustomrDto.class, sql)
|
||||
* Ebean.findDto(CustomerDto.class, sql)
|
||||
* .setParameter("name", "Acme%")
|
||||
* .setParameter("status", "ACTIVE")
|
||||
* .findList();
|
||||
|
||||
@@ -570,7 +570,7 @@ public interface EbeanServer {
|
||||
* txn.setBatchGetGeneratedKeys(false);
|
||||
*
|
||||
* // explicitly flush the JDBC batch buffer
|
||||
* txn.flushBatch();
|
||||
* txn.flush();
|
||||
*
|
||||
* ...
|
||||
*
|
||||
@@ -688,8 +688,6 @@ public interface EbeanServer {
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* </p>
|
||||
*/
|
||||
void endTransaction();
|
||||
|
||||
@@ -963,7 +961,7 @@ public interface EbeanServer {
|
||||
* updateSql.getRowCount().
|
||||
* <p>
|
||||
* If you wish to execute a Sql Select natively then you should use the
|
||||
* FindByNativeSql object.
|
||||
* SqlQuery object or DtoQuery.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the table modification information is automatically deduced and
|
||||
@@ -1330,9 +1328,8 @@ public interface EbeanServer {
|
||||
* u1.setName("u1 mod");
|
||||
* u2.setName("u2 mod");
|
||||
*
|
||||
* ebeanServer.save(u1);
|
||||
* ebeanServer.save(u2);
|
||||
*
|
||||
* u1.save();
|
||||
* u2.save();
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
|
||||
@@ -64,6 +64,17 @@ public class Expr {
|
||||
return Ebean.getExpressionFactory().ieq(propertyName, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* In Range - property >= value1 and property < value2.
|
||||
* <p>
|
||||
* Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps.
|
||||
* </p>
|
||||
*/
|
||||
public static Expression inRange(String propertyName, Object value1, Object value2) {
|
||||
|
||||
return Ebean.getExpressionFactory().inRange(propertyName, value1, value2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
|
||||
@@ -149,6 +149,14 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression ineObject(String propertyName, Object value);
|
||||
|
||||
/**
|
||||
* In Range - property >= value1 and property < value2.
|
||||
* <p>
|
||||
* Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps.
|
||||
* </p>
|
||||
*/
|
||||
Expression inRange(String propertyName, Object value1, Object value2);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
|
||||
@@ -128,6 +128,38 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Return the underlying query as an UpdateQuery.
|
||||
* <p>
|
||||
* Typically this is used with query beans to covert a query bean
|
||||
* query into an UpdateQuery like the examples below.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QCustomer()
|
||||
* .name.startsWith("Rob")
|
||||
* .asUpdate()
|
||||
* .set("active", false)
|
||||
* .update();;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QContact()
|
||||
* .notes.note.startsWith("Make Inactive")
|
||||
* .email.endsWith("@foo.com")
|
||||
* .customer.id.equalTo(42)
|
||||
* .asUpdate()
|
||||
* .set("inactive", true)
|
||||
* .setRaw("email = lower(email)")
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
UpdateQuery<T> asUpdate();
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
@@ -194,6 +226,32 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
int update(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning true if a row is found.
|
||||
* <p>
|
||||
* The query is executed using max rows of 1 and will only select the id property.
|
||||
* This method is really just a convenient way to optimise a query to perform a
|
||||
* 'does a row exist in the db' check.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists = query().where().eq("email", "rob@foo.com").exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h2>Example using a query bean:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return True if the query finds a matching row in the database
|
||||
*/
|
||||
boolean exists();
|
||||
|
||||
/**
|
||||
* Execute the query iterating over the results.
|
||||
*
|
||||
@@ -258,7 +316,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
*
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -269,7 +327,7 @@ public interface ExpressionList<T> {
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
*
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -291,7 +349,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Execute a query returning a single value of a single property/column.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String name =
|
||||
@@ -376,7 +433,6 @@ public interface ExpressionList<T> {
|
||||
* 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)
|
||||
@@ -515,7 +571,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
@@ -625,7 +680,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Equal to expression for the value at the given path in the JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonEqualTo("content", "path.other", 34)
|
||||
@@ -640,7 +694,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Not Equal to - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonNotEqualTo("content", "path.other", 34)
|
||||
@@ -655,7 +708,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Greater than - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonGreaterThan("content", "path.other", 34)
|
||||
@@ -666,7 +718,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Greater than or equal to - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonGreaterOrEqual("content", "path.other", 34)
|
||||
@@ -677,7 +728,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Less than - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonLessThan("content", "path.other", 34)
|
||||
@@ -688,7 +738,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Less than or equal to - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonLessOrEqualTo("content", "path.other", 34)
|
||||
@@ -699,7 +748,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Between - for the given path in a JSON document.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonBetween("content", "orderDate", lowerDateTime, upperDateTime)
|
||||
@@ -713,7 +761,6 @@ public interface ExpressionList<T> {
|
||||
* <p>
|
||||
* This returns the list so that add() can be chained.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Query<Customer> query = Ebean.find(Customer.class);
|
||||
@@ -756,8 +803,16 @@ public interface ExpressionList<T> {
|
||||
ExpressionList<T> ine(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
* In Range - property >= value1 and property < value2.
|
||||
* <p>
|
||||
* Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> inRange(String propertyName, Object value1, Object value2);
|
||||
|
||||
/**
|
||||
* Between - property between the two given values.
|
||||
*/
|
||||
ExpressionList<T> between(String propertyName, Object value1, Object value2);
|
||||
|
||||
/**
|
||||
@@ -810,7 +865,6 @@ public interface ExpressionList<T> {
|
||||
* To get control over the options you can create an ExampleExpression and set
|
||||
* those options such as case insensitive etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // create an example bean and set the properties
|
||||
@@ -819,7 +873,7 @@ public interface ExpressionList<T> {
|
||||
* example.setName("Rob%");
|
||||
* example.setNotes("%something%");
|
||||
*
|
||||
* List<Customer> list = Ebean.find(Customer.class).where()
|
||||
* List<Customer> list = Ebean.find(Customer.class).where()
|
||||
* // pass the bean into the where() clause
|
||||
* .exampleLike(example)
|
||||
* // you can add other expressions to the same query
|
||||
@@ -828,7 +882,7 @@ public interface ExpressionList<T> {
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Similarly you can create an ExampleExpression
|
||||
* <p>
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Customer example = new Customer();
|
||||
@@ -1050,7 +1104,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
@@ -1064,7 +1117,6 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Add expression for the given bit flags to be NOT set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
|
||||
@@ -1115,6 +1167,13 @@ public interface ExpressionList<T> {
|
||||
* raw("add_days(orderDate, 10) < ?", someDate)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Subquery example:</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
* .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> raw(String raw, Object value);
|
||||
|
||||
@@ -1139,12 +1198,18 @@ public interface ExpressionList<T> {
|
||||
* then they are not translated. logical property name names (not fully
|
||||
* qualified) will still be translated to their physical name.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* raw("orderQty < shipQty")
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Subquery example:</h4>
|
||||
* <pre>{@code
|
||||
*
|
||||
* .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> raw(String raw);
|
||||
|
||||
@@ -1218,12 +1283,10 @@ public interface ExpressionList<T> {
|
||||
* typically you only explicitly need to use the and() junction
|
||||
* when it is nested inside an or() or not() junction.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: Nested and()
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .or()
|
||||
* .and() // nested and
|
||||
@@ -1244,16 +1307,27 @@ public interface ExpressionList<T> {
|
||||
* Return a list of expressions that will be joined by OR's.
|
||||
* This is exactly the same as disjunction();
|
||||
* <p>
|
||||
* <p>
|
||||
* Use endOr() or endJunction() to end the OR junction.
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: (status active OR anniversary is null)
|
||||
*
|
||||
* .where()
|
||||
* .or()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .isNull("anniversary")
|
||||
* .orderBy().asc("name")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: Use or() to join
|
||||
* // two nested and() expressions
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .or()
|
||||
* .and()
|
||||
@@ -1277,8 +1351,8 @@ public interface ExpressionList<T> {
|
||||
* Use endNot() or endJunction() to end expressions being added to the
|
||||
* NOT expression list.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>@{code
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* .where()
|
||||
* .not()
|
||||
@@ -1287,12 +1361,11 @@ public interface ExpressionList<T> {
|
||||
* .endNot()
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <pre>@{code
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Example: nested not()
|
||||
*
|
||||
* Ebean.find(Customer.class)
|
||||
* .where()
|
||||
* .eq("status", Customer.Status.ACTIVE)
|
||||
* .not()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
@@ -42,6 +45,32 @@ public interface ExtendedServer {
|
||||
*/
|
||||
void setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* Execute the query returning true if a row is found.
|
||||
* <p>
|
||||
* The query is executed using max rows of 1 and will only select the id property.
|
||||
* This method is really just a convenient way to optimise a query to perform a
|
||||
* 'does a row exist in the db' check.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists = query().where().eq("email", "rob@foo.com").exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h2>Example using a query bean:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return True if the query finds a matching row in the database
|
||||
*/
|
||||
<T> boolean exists(SpiQuery<?> ormQuery, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.ebean.util.StringHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -412,15 +411,15 @@ public final class OrderBy<T> implements Serializable {
|
||||
|
||||
String[] chunks = orderByClause.split(",");
|
||||
for (String chunk : chunks) {
|
||||
String[] pairs = chunk.split(" ");
|
||||
Property p = parseProperty(pairs);
|
||||
Property p = parseProperty(chunk);
|
||||
if (p != null) {
|
||||
list.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Property parseProperty(String[] pairs) {
|
||||
private Property parseProperty(String chunk) {
|
||||
String[] pairs = chunk.split(" ");
|
||||
if (pairs.length == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -446,8 +445,7 @@ public final class OrderBy<T> implements Serializable {
|
||||
boolean asc = isAscending(wordList.get(1));
|
||||
return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3));
|
||||
}
|
||||
String m = "Expecting a 1, 2 or 4 words in [" + Arrays.toString(pairs) + "] but got " + wordList;
|
||||
throw new RuntimeException(m);
|
||||
return new Property(chunk.trim(), true);
|
||||
}
|
||||
|
||||
private boolean isAscending(String s) {
|
||||
|
||||
@@ -244,6 +244,38 @@ public interface Query<T> {
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Convert the query to a UpdateQuery.
|
||||
* <p>
|
||||
* Typically this is used with query beans to covert a query bean
|
||||
* query into an UpdateQuery like the examples below.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QCustomer()
|
||||
* .name.startsWith("Rob")
|
||||
* .asUpdate()
|
||||
* .set("active", false)
|
||||
* .update();;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QContact()
|
||||
* .notes.note.startsWith("Make Inactive")
|
||||
* .email.endsWith("@foo.com")
|
||||
* .customer.id.equalTo(42)
|
||||
* .asUpdate()
|
||||
* .set("inactive", true)
|
||||
* .setRaw("email = lower(email)")
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
UpdateQuery<T> asUpdate();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -340,6 +372,27 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setAutoTune(boolean autoTune);
|
||||
|
||||
/**
|
||||
* Execute the query allowing properties with invalid JSON to be collected and not fail the query.
|
||||
* <pre>{@code
|
||||
*
|
||||
* // fetch a bean with JSON content
|
||||
* EBasicJsonList bean= Ebean.find(EBasicJsonList.class)
|
||||
* .setId(42)
|
||||
* .setAllowLoadErrors() // collect errors into bean state if we have invalid JSON
|
||||
* .findOne();
|
||||
*
|
||||
*
|
||||
* // get the invalid JSON errors from the bean state
|
||||
* Map<String, Exception> errors = server().getBeanState(bean).getLoadErrors();
|
||||
*
|
||||
* // If this map is not empty tell we have invalid JSON
|
||||
* // and should try and fix the JSON content or inform the user
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setAllowLoadErrors();
|
||||
|
||||
/**
|
||||
* Set the default lazy loading batch size to use.
|
||||
* <p>
|
||||
@@ -636,15 +689,13 @@ public interface Query<T> {
|
||||
* .where().eq("status", Status.NEW)
|
||||
* .order().asc("id");
|
||||
*
|
||||
* QueryIterator<Customer> it = query.findIterate();
|
||||
* try {
|
||||
* // use try with resources to ensure QueryIterator is closed
|
||||
*
|
||||
* try (QueryIterator<Customer> it = query.findIterate()) {
|
||||
* while (it.hasNext()) {
|
||||
* Customer customer = it.next();
|
||||
* // do something with customer ...
|
||||
* }
|
||||
* } finally {
|
||||
* // close the underlying resources
|
||||
* it.close();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -838,6 +889,37 @@ public interface Query<T> {
|
||||
*/
|
||||
boolean isCountDistinct();
|
||||
|
||||
/**
|
||||
* Execute the query returning true if a row is found.
|
||||
* <p>
|
||||
* The query is executed using max rows of 1 and will only select the id property.
|
||||
* This method is really just a convenient way to optimise a query to perform a
|
||||
* 'does a row exist in the db' check.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Example using a query bean:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists =
|
||||
* new QContact()
|
||||
* .email.equalTo("rob@foo.com")
|
||||
* .exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h2>Example:</h2>
|
||||
* <pre>{@code
|
||||
*
|
||||
* boolean userExists = query()
|
||||
* .where().eq("email", "rob@foo.com")
|
||||
* .exists();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return True if the query finds a matching row in the database
|
||||
*/
|
||||
boolean exists();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -1329,7 +1411,6 @@ public interface Query<T> {
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
@@ -1541,11 +1622,50 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> alias(String alias);
|
||||
|
||||
/**
|
||||
* Set the base table to use for this query.
|
||||
* <p>
|
||||
* Typically this is used when a table has partitioning and we wish to specify a specific
|
||||
* partition/table to query against.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* QOrder()
|
||||
* .setBaseTable("order_2019_05")
|
||||
* .status.equalTo(Status.NEW)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Query<T> setBaseTable(String baseTable);
|
||||
|
||||
/**
|
||||
* Return the type of beans being queried.
|
||||
*/
|
||||
Class<T> getBeanType();
|
||||
|
||||
/**
|
||||
* Restrict the query to only return subtypes of the given inherit type.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Animal> animals =
|
||||
* new QAnimal()
|
||||
* .name.startsWith("Fluffy")
|
||||
* .setInheritType(Cat.class)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type An inheritance subtype of the
|
||||
*/
|
||||
Query<T> setInheritType(Class<? extends T> type);
|
||||
|
||||
/**
|
||||
* Returns the inherit type. This is normally the same as getBeanType() returns as long as no other type is set.
|
||||
*/
|
||||
Class<? extends T> getInheritType();
|
||||
|
||||
/**
|
||||
* Return the type of query being executed.
|
||||
*/
|
||||
|
||||
@@ -195,6 +195,31 @@ public interface SqlQuery extends Serializable {
|
||||
*/
|
||||
SqlQuery setParameter(String name, Object value);
|
||||
|
||||
/**
|
||||
* Set one of more positioned parameters.
|
||||
* <p>
|
||||
* This is a convenient alternative to multiple calls setParameter().
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name from customer where name like ? and status = ?";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .setParams("Rob", Status.NEW)
|
||||
* .findList();
|
||||
*
|
||||
*
|
||||
* // is the same as ...
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, "Rob")
|
||||
* .setParameter(2, "Status.NEW)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
SqlQuery setParams(Object... values);
|
||||
|
||||
/**
|
||||
* The same as bind for positioned parameters.
|
||||
*/
|
||||
|
||||
@@ -217,6 +217,33 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set one of more positioned parameters.
|
||||
* <p>
|
||||
* This is a convenient alternative to multiple calls setParameter().
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "insert into audit_log (id, name, version) values (?,?,?)";
|
||||
*
|
||||
* Ebean.createSqlUpdate(sql)
|
||||
* .setParams(UUID.randomUUID(), "Hello", 1)
|
||||
* .executeNow();
|
||||
*
|
||||
*
|
||||
* // is the same as ...
|
||||
*
|
||||
* Ebean.createSqlUpdate(sql)
|
||||
* .setParameter(1, UUID.randomUUID())
|
||||
* .setParameter(2, "Hello")
|
||||
* .setParameter(3, 1)
|
||||
* .executeNow();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
SqlUpdate setParams(Object... values);
|
||||
|
||||
/**
|
||||
* Set the next positioned parameter.
|
||||
*
|
||||
|
||||
@@ -244,8 +244,7 @@ public interface Transaction extends AutoCloseable {
|
||||
*
|
||||
* // assume Customer has L2 bean caching enabled ...
|
||||
*
|
||||
* Transaction transaction = Ebean.beginTransaction();
|
||||
* try {
|
||||
* try (Transaction transaction = ebeanServer.beginTransaction()) {
|
||||
*
|
||||
* // this uses L2 bean cache as the transaction
|
||||
* // ... is considered "query only" at this point
|
||||
@@ -253,7 +252,7 @@ public interface Transaction extends AutoCloseable {
|
||||
*
|
||||
* // transaction no longer "query only" once
|
||||
* // ... a bean has been saved etc
|
||||
* Ebean.save(someBean);
|
||||
* someBean.save();
|
||||
*
|
||||
* // will NOT use L2 bean cache as the transaction
|
||||
* // ... is no longer considered "query only"
|
||||
@@ -273,8 +272,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* Customer.find.byId(99); // skips l2 bean cache
|
||||
*
|
||||
*
|
||||
* } finally {
|
||||
* transaction.end();
|
||||
* transaction.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -290,36 +288,66 @@ public interface Transaction extends AutoCloseable {
|
||||
boolean isSkipCache();
|
||||
|
||||
/**
|
||||
* Turn on or off statement batching. Statement batching can be transparent
|
||||
* for drivers and databases that support getGeneratedKeys. Otherwise you may
|
||||
* wish to specifically control when batching is used via this method.
|
||||
* <p>
|
||||
* Refer to <code>java.sql.PreparedStatement.addBatch();</code>
|
||||
* <p>
|
||||
* Note that you may also wish to use the setPersistCascade method to stop
|
||||
* save and delete cascade behaviour. You may do this to have full control
|
||||
* over the order of execution rather than the normal cascading fashion.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the <em>execution order</em> in batch mode may be different from
|
||||
* non batch mode execution order. Also note that <em>insert behaviour</em>
|
||||
* may be different depending on the JDBC driver and its support for
|
||||
* getGeneratedKeys. That is, for JDBC drivers that do not support
|
||||
* getGeneratedKeys you may not get back the generated IDs (used for inserting
|
||||
* associated detail beans etc).
|
||||
* </p>
|
||||
* Turn on or off use of JDBC statement batching.
|
||||
* <p>
|
||||
* Calls to save(), delete(), insert() and execute() all support batch
|
||||
* processing. This includes normal beans, MapBean, CallableSql and UpdateSql.
|
||||
* processing. This includes normal beans, CallableSql and UpdateSql.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction transaction = server.beginTransaction()) {
|
||||
*
|
||||
* // turn on JDBC batch
|
||||
* transaction.setBatchMode(true);
|
||||
*
|
||||
* // tune the batch size
|
||||
* transaction.setBatchSize(50);
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* transaction.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>getGeneratedKeys</h3>
|
||||
* <p>
|
||||
* The flushing of the batched statements is automatic but you can call
|
||||
* batchFlush when you like. Note that flushing occurs when a query is
|
||||
* executed or when you mix UpdateSql and CallableSql with save and delete of
|
||||
* Often with large batch inserts we want to turn off getGeneratedKeys. We do
|
||||
* this via {@link #setBatchGetGeneratedKeys(boolean)}.
|
||||
* Also note that some JDBC drivers do not support getGeneratedKeys in JDBC batch mode.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction transaction = server.beginTransaction()) {
|
||||
*
|
||||
* transaction.setBatchMode(true);
|
||||
* transaction.setBatchSize(100);
|
||||
* // insert but don't bother getting back the generated keys
|
||||
* transaction.setBatchGetGeneratedKeys(false);
|
||||
*
|
||||
*
|
||||
* // perform lots of inserts ...
|
||||
* ...
|
||||
*
|
||||
* transaction.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Flush</h3>
|
||||
* <p>
|
||||
* The batch is automatically flushed when it hits the batch size and also when we
|
||||
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
|
||||
* beans.
|
||||
* </p>
|
||||
* <p>
|
||||
* Example: batch processing executing every 3 rows
|
||||
* We use {@link #flush()} to explicitly flush the batch and we can use
|
||||
* {@link #setBatchFlushOnQuery(boolean)} and {@link #setBatchFlushOnMixed(boolean)}
|
||||
* to control the automatic flushing behaviour.
|
||||
* </p>
|
||||
* <p>
|
||||
* Example: batch processing of CallableSql executing every 10 rows
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
@@ -334,24 +362,21 @@ public interface Transaction extends AutoCloseable {
|
||||
* CallableSql cs = new CallableSql(sql);
|
||||
* cs.registerOut(2, Types.INTEGER);
|
||||
*
|
||||
* // (optional) inform eBean this stored procedure
|
||||
* // (optional) inform Ebean this stored procedure
|
||||
* // inserts into a table called sp_test
|
||||
* cs.addModification("sp_test", true, false, false);
|
||||
*
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* txn.setBatchMode(true);
|
||||
* txn.setBatchSize(3);
|
||||
* try {
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
* txn.setBatchMode(true);
|
||||
* txn.setBatchSize(10);
|
||||
*
|
||||
* for (int i = 0; i < da.length;) {
|
||||
* cs.setParameter(1, da[i]);
|
||||
* ebeanServer.execute(cs);
|
||||
* }
|
||||
*
|
||||
* // NB: commit implicitly flushes
|
||||
* // Note: commit implicitly flushes
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
|
||||
@@ -154,9 +154,19 @@ public interface UpdateQuery<T> {
|
||||
*/
|
||||
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set the label on the update query.
|
||||
*/
|
||||
UpdateQuery<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return the query expression list to add predicates to.
|
||||
*/
|
||||
ExpressionList<T> where();
|
||||
|
||||
/**
|
||||
* Execute the update returning the number of rows updated.
|
||||
*/
|
||||
int update();
|
||||
|
||||
}
|
||||
|
||||
@@ -98,6 +98,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
private Object[] origValues;
|
||||
|
||||
private Exception[] loadErrors;
|
||||
|
||||
private int lazyLoadProperty = -1;
|
||||
|
||||
private Object ownerId;
|
||||
@@ -1115,4 +1117,36 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public void setSortOrder(int sortOrder) {
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the load error that happened on this property.
|
||||
*/
|
||||
public void setLoadError(int propertyIndex, Exception t) {
|
||||
if (loadErrors == null) {
|
||||
loadErrors = new Exception[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
loadErrors[propertyIndex] = t;
|
||||
flags[propertyIndex] |= FLAG_LOADED_PROP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the loadErrors.
|
||||
*/
|
||||
public Map<String, Exception> getLoadErrors() {
|
||||
if (loadErrors == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Exception> ret = null;
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
Exception loadError = loadErrors[i];
|
||||
if (loadError != null) {
|
||||
if (ret == null) {
|
||||
ret = new LinkedHashMap<>();
|
||||
}
|
||||
ret.put(getProperty(i), loadError);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ public class ServerCacheNotification {
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ts:" + modifyTimestamp + " tables:" + dependentTables;
|
||||
}
|
||||
|
||||
public long getModifyTimestamp() {
|
||||
return modifyTimestamp;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ServerCacheOptions {
|
||||
private int maxIdleSecs;
|
||||
private int maxSecsToLive;
|
||||
private int trimFrequency;
|
||||
private boolean nearCache;
|
||||
|
||||
/**
|
||||
* Construct with no set options.
|
||||
@@ -40,6 +41,14 @@ public class ServerCacheOptions {
|
||||
this.trimFrequency = cacheTuning.trimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with nearCache option.
|
||||
*/
|
||||
public ServerCacheOptions(boolean nearCache, CacheBeanTuning tuning) {
|
||||
this(tuning);
|
||||
this.nearCache = nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any settings from the default settings that have not already been
|
||||
* specifically set.
|
||||
@@ -70,9 +79,33 @@ public class ServerCacheOptions {
|
||||
copy.maxIdleSecs = maxIdleSecs;
|
||||
copy.maxSecsToLive = maxSecsToLive;
|
||||
copy.trimFrequency = trimFrequency;
|
||||
copy.nearCache = this.nearCache;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this object with nearCache option.
|
||||
*/
|
||||
public ServerCacheOptions copy(boolean nearCache) {
|
||||
ServerCacheOptions copy = copy();
|
||||
copy.nearCache = nearCache;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if nearCache was explicitly turned on.
|
||||
*/
|
||||
public boolean isNearCache() {
|
||||
return nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on nearCache option.
|
||||
*/
|
||||
public void setNearCache(boolean nearCache) {
|
||||
this.nearCache = nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum cache size.
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,6 @@ import io.ebean.annotation.Platform;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -33,6 +32,8 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected String migrationPath = "dbmigration";
|
||||
|
||||
protected String migrationInitPath = "dbinit";
|
||||
|
||||
/**
|
||||
* Subdirectory the model xml files go into.
|
||||
*/
|
||||
@@ -118,6 +119,15 @@ public class DbMigrationConfig {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path for normal migrations or dbinit migrations.
|
||||
*
|
||||
* @param dbinitMigration When true return the path for dbinit migrations.
|
||||
*/
|
||||
public String getMigrationPath(boolean dbinitMigration) {
|
||||
return dbinitMigration ? migrationInitPath : migrationPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resource path for db migrations.
|
||||
*/
|
||||
@@ -419,6 +429,7 @@ public class DbMigrationConfig {
|
||||
public void loadSettings(PropertiesWrapper properties, String serverName) {
|
||||
|
||||
migrationPath = properties.get("migration.migrationPath", migrationPath);
|
||||
migrationInitPath = properties.get("migration.migrationInitPath", migrationInitPath);
|
||||
modelPath = properties.get("migration.modelPath", modelPath);
|
||||
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
|
||||
applySuffix = properties.get("migration.applySuffix", applySuffix);
|
||||
@@ -472,6 +483,7 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setMetaTable(metaTable);
|
||||
runnerConfig.setApplySuffix(applySuffix);
|
||||
runnerConfig.setMigrationPath(migrationPath);
|
||||
runnerConfig.setMigrationInitPath(migrationInitPath);
|
||||
runnerConfig.setRunPlaceholderMap(runPlaceholderMap);
|
||||
runnerConfig.setRunPlaceholders(runPlaceholders);
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
|
||||
@@ -252,6 +252,8 @@ public class ServerConfig {
|
||||
|
||||
private boolean ddlRun;
|
||||
|
||||
private boolean ddlExtra = true;
|
||||
|
||||
private boolean ddlCreateOnly;
|
||||
|
||||
private String ddlInitSql;
|
||||
@@ -366,9 +368,10 @@ public class ServerConfig {
|
||||
private UuidVersion uuidVersion = UuidVersion.VERSION4;
|
||||
|
||||
/**
|
||||
* The UUID state file (for Version 1 UUIDs).
|
||||
* The UUID state file (for Version 1 UUIDs). By default, the file is created in
|
||||
* ${HOME}/.ebean/${servername}-uuid.state
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
private String uuidStateFile;
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
@@ -486,6 +489,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean notifyL2CacheInForeground;
|
||||
|
||||
/**
|
||||
* Set to true to support query plan capture.
|
||||
*/
|
||||
private boolean collectQueryPlans;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
@@ -594,6 +602,47 @@ public class ServerConfig {
|
||||
return serviceObject.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a service object into configuration such that it can be passed to a plugin.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* JedisPool jedisPool = ..
|
||||
*
|
||||
* serverConfig.putServiceObject(jedisPool);
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public void putServiceObject(Object configObject) {
|
||||
String key = serviceObjectKey(configObject);
|
||||
serviceObject.put(key, configObject);
|
||||
}
|
||||
|
||||
private String serviceObjectKey(Object configObject) {
|
||||
return serviceObjectKey(configObject.getClass());
|
||||
}
|
||||
|
||||
private String serviceObjectKey(Class<?> cls) {
|
||||
String simpleName = cls.getSimpleName();
|
||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by plugins to obtain service objects.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* JedisPool jedisPool = serverConfig.getServiceObject(JedisPool.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param cls The type of the service object to obtain
|
||||
* @return The service object given the class type
|
||||
*/
|
||||
public <P> P getServiceObject(Class<P> cls) {
|
||||
return (P) serviceObject.get(serviceObjectKey(cls));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson JsonFactory to use.
|
||||
* <p>
|
||||
@@ -2069,6 +2118,16 @@ public class ServerConfig {
|
||||
this.ddlRun = ddlRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false if you not want to run the extra-ddl.xml scripts. (default = true)
|
||||
* <p>
|
||||
* Typically we want this on when we are running tests.
|
||||
*/
|
||||
public void setDdlExtra(boolean ddlExtra) {
|
||||
this.ddlExtra = ddlExtra;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the "drop all ddl" should be skipped.
|
||||
* <p>
|
||||
@@ -2139,6 +2198,13 @@ public class ServerConfig {
|
||||
return ddlRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if extra-ddl.xml should be executed.
|
||||
*/
|
||||
public boolean isDdlExtra() {
|
||||
return ddlExtra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the class path search should be disabled.
|
||||
*/
|
||||
@@ -2832,6 +2898,7 @@ public class ServerConfig {
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
|
||||
@@ -2914,6 +2981,7 @@ public class ServerConfig {
|
||||
|
||||
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
|
||||
ddlRun = p.getBoolean("ddl.run", ddlRun);
|
||||
ddlExtra = p.getBoolean("ddl.extra", ddlExtra);
|
||||
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
|
||||
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
|
||||
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
|
||||
@@ -3197,6 +3265,20 @@ public class ServerConfig {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if query plan capture is enabled.
|
||||
*/
|
||||
public boolean isCollectQueryPlans() {
|
||||
return collectQueryPlans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable query plan capture.
|
||||
*/
|
||||
public void setCollectQueryPlans(boolean collectQueryPlans) {
|
||||
this.collectQueryPlans = collectQueryPlans;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -50,6 +50,11 @@ public interface DbMigration {
|
||||
throw new IllegalStateException("No service implementation found for DbMigration?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false to suppress logging to System out.
|
||||
*/
|
||||
void setLogToSystemOut(boolean logToSystemOut);
|
||||
|
||||
/**
|
||||
* Set the path from the current working directory to the application resources.
|
||||
* <p>
|
||||
@@ -154,7 +159,7 @@ public interface DbMigration {
|
||||
List<String> getPendingDrops();
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
* Generate the next migration sql script and associated model xml.
|
||||
* <p>
|
||||
* This does not run the migration or ddl scripts but just generates them.
|
||||
* </p>
|
||||
@@ -186,4 +191,15 @@ public interface DbMigration {
|
||||
* @return the version of the generated migration or null
|
||||
*/
|
||||
String generateMigration() throws IOException;
|
||||
|
||||
/**
|
||||
* Generate an "init" migration which has all changes.
|
||||
* <p>
|
||||
* An "init" migration can only be executed and used on a database that has had no
|
||||
* prior migrations run on it.
|
||||
* </p>
|
||||
* @return the version of the generated migration
|
||||
*/
|
||||
String generateInitMigration() throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ import java.util.List;
|
||||
*/
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Collect query plans.
|
||||
*/
|
||||
List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request);
|
||||
|
||||
/**
|
||||
* Visit the metrics resetting and collecting/reporting as desired.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
public interface MetaQueryPlan {
|
||||
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
String getLabel();
|
||||
|
||||
String getSql();
|
||||
|
||||
String getBind();
|
||||
|
||||
String getPlan();
|
||||
|
||||
long getQueryTimeMicros();
|
||||
|
||||
long getCaptureCount();
|
||||
}
|
||||
@@ -25,6 +25,11 @@ public enum MetricType {
|
||||
* <p>
|
||||
* SqlQuery and SqlUpdate without a label have no metrics collected.
|
||||
*/
|
||||
SQL
|
||||
SQL,
|
||||
|
||||
/**
|
||||
* L2 cache metrics.
|
||||
*/
|
||||
L2
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Request used to capture query plans.
|
||||
*/
|
||||
public class QueryPlanRequest {
|
||||
|
||||
private List<MetaQueryPlan> plans = new ArrayList<>();
|
||||
|
||||
private Connection connection;
|
||||
|
||||
private boolean store;
|
||||
|
||||
private long since;
|
||||
|
||||
private Set<Class<?>> includedBeanTypes;
|
||||
|
||||
private Set<String> includedLabels;
|
||||
|
||||
public List<MetaQueryPlan> getPlans() {
|
||||
return plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connection to use to capture the query plans.
|
||||
*/
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection to use to capture the query plans.
|
||||
*/
|
||||
public void setConnection(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the captured query plan is stored.
|
||||
*/
|
||||
public boolean isStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to store the captured query plan.
|
||||
*/
|
||||
public void setStore(boolean store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the epoch time after which the query plan was capture (to be included).
|
||||
*/
|
||||
public long getSince() {
|
||||
return since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the epoch time after which the query plan was captured.
|
||||
* <p>
|
||||
* This is used to only capture plans that have changed since a given time (like the time of last capture).
|
||||
* </p>
|
||||
*
|
||||
* @param since The time after which the query plan was captured to be included
|
||||
*/
|
||||
public void setSince(long since) {
|
||||
this.since = since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process consume the query plan.
|
||||
*/
|
||||
public void process(MetaQueryPlan plan) {
|
||||
plans.add(plan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean type should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeType(Class<?> beanType) {
|
||||
return includedBeanTypes == null || includedBeanTypes.isEmpty() || includedBeanTypes.contains(beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the label should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeLabel(String label) {
|
||||
return includedLabels == null || includedLabels.isEmpty() || includedLabels.contains(label);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebeaninternal.server.profile.DMetricFactory;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
@@ -17,6 +17,16 @@ public interface TimedMetric {
|
||||
*/
|
||||
void add(long micros, long beans);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos.
|
||||
*/
|
||||
void addSinceNanos(long startNanos);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos and bean count.
|
||||
*/
|
||||
void addSinceNanos(long startNanos, long beans);
|
||||
|
||||
/**
|
||||
* Return true if there are no metrics collected since the last collection.
|
||||
*/
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
@@ -7,6 +7,16 @@ import io.ebean.meta.MetricVisitor;
|
||||
*/
|
||||
public interface TimedMetricMap {
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos.
|
||||
*/
|
||||
void addSinceNanos(String key, long startNanos);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos and beans.
|
||||
*/
|
||||
void addSinceNanos(String key, long startNanos, int beans);
|
||||
|
||||
/**
|
||||
* Add an execution for the given key.
|
||||
*/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebean.plugin;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
/**
|
||||
* Errorhandler to handle load errors and may be recover correct value.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LoadErrorHandler {
|
||||
void handleLoadError(EntityBean bean, Property prop, String fullName, Exception e);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package io.ebean.text;
|
||||
public class TextException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1601310159486033148L;
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* Construct with an error message.
|
||||
@@ -27,4 +28,41 @@ public class TextException extends RuntimeException {
|
||||
public TextException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a detailed exception.
|
||||
*
|
||||
* @param message
|
||||
* the message. The placeholder {} will be replaced by
|
||||
* <code>text</code>
|
||||
* @param text
|
||||
* the fault text.
|
||||
* @param cause
|
||||
* the case
|
||||
*/
|
||||
public TextException(String message, String text, Exception cause) {
|
||||
super(message.replace("{}", String.valueOf(text)), cause);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for a detailed exception.
|
||||
*
|
||||
* @param message
|
||||
* the message. The placeholder {} will be replaced by
|
||||
* <code>text</code>
|
||||
* @param text
|
||||
* the fault text.
|
||||
*/
|
||||
public TextException(String message, String text) {
|
||||
super(message.replace("{}", String.valueOf(text)));
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the text, that caused the error. (e.g. the JSON). May be null.
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ public class CamelCaseHelper {
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
lastUpper = i;
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
|
||||
@@ -11,4 +11,8 @@ public interface CQueryPlanKey {
|
||||
*/
|
||||
String getPartialKey();
|
||||
|
||||
/**
|
||||
* Return a query plan key taking into account persist cascading.
|
||||
*/
|
||||
CQueryPlanKey withDeleteByIds();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.Pairs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -439,6 +439,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
void setDelete();
|
||||
|
||||
/**
|
||||
* Set the query to be delete by ids due to cascading delete.
|
||||
*/
|
||||
CQueryPlanKey setDeleteByIdsPlan();
|
||||
|
||||
/**
|
||||
* Set the query to select the id property only.
|
||||
*/
|
||||
@@ -749,23 +754,6 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isDisableLazyLoading();
|
||||
|
||||
/**
|
||||
* Internally set by Ebean when this query must use the DISTINCT keyword.
|
||||
* <p>
|
||||
* This does not exclude/remove the use of the id property.
|
||||
*/
|
||||
void setSqlDistinct(boolean sqlDistinct);
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user or internally by Ebean to use DISTINCT.
|
||||
*/
|
||||
boolean isDistinctQuery();
|
||||
|
||||
/**
|
||||
* Return true if this was internally set to sql distinct (ie. many where predicate).
|
||||
*/
|
||||
boolean isSqlDistinct();
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user to use DISTINCT.
|
||||
*/
|
||||
@@ -837,6 +825,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Return the base table to use if user defined on the query.
|
||||
*/
|
||||
String getBaseTable();
|
||||
|
||||
/**
|
||||
* Return root table alias set by {@link #alias(String)} command.
|
||||
*/
|
||||
@@ -861,4 +854,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
* Returns the count distinct order setting.
|
||||
*/
|
||||
CountDistinctOrder getCountDistinctOrder();
|
||||
|
||||
/**
|
||||
* Handles load errors.
|
||||
*/
|
||||
void handleLoadError(String fullName, Exception e);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class DdlGenerator {
|
||||
|
||||
private final boolean generateDdl;
|
||||
private final boolean runDdl;
|
||||
private final boolean extraDdl;
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
private final boolean ddlCommitOnCreateIndex;
|
||||
@@ -54,6 +55,7 @@ public class DdlGenerator {
|
||||
this.server = server;
|
||||
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.extraDdl = serverConfig.isDdlExtra();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
this.dbSchema = serverConfig.getDbSchema();
|
||||
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
|
||||
@@ -166,8 +168,7 @@ public class DdlGenerator {
|
||||
|
||||
protected void runDropSql(Connection connection) throws IOException {
|
||||
if (!createOnly) {
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
if (extraDdl && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
|
||||
if (extraApply != null) {
|
||||
runScript(connection, false, extraApply, "extra-ddl");
|
||||
@@ -187,8 +188,7 @@ public class DdlGenerator {
|
||||
}
|
||||
runScript(connection, false, createAllContent, getCreateFileName());
|
||||
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
if (extraDdl && jaxbPresent) {
|
||||
if (currentModel.isTablePartitioning()) {
|
||||
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
|
||||
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
|
||||
|
||||
@@ -72,6 +72,8 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
|
||||
|
||||
private boolean logToSystemOut = true;
|
||||
|
||||
/**
|
||||
* Set to true if DefaultDbMigration run with online EbeanServer instance.
|
||||
*/
|
||||
@@ -271,6 +273,15 @@ public class DefaultDbMigration implements DbMigration {
|
||||
*/
|
||||
@Override
|
||||
public String generateMigration() throws IOException {
|
||||
return generateMigrationFor(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateInitMigration() throws IOException {
|
||||
return generateMigrationFor(true);
|
||||
}
|
||||
|
||||
private String generateMigrationFor(boolean dbinitMigration) throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
if (!online) {
|
||||
@@ -286,14 +297,17 @@ public class DefaultDbMigration implements DbMigration {
|
||||
configurePlatforms();
|
||||
}
|
||||
try {
|
||||
Request request = createRequest();
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
|
||||
} else {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(request.migrationDir, pair.prefix);
|
||||
generateExtraDdl(subPath, pair.platform, request.isTablePartitioning());
|
||||
Request request = createRequest(dbinitMigration);
|
||||
if (!dbinitMigration) {
|
||||
// repeatable migrations
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
|
||||
} else {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(request.migrationDir, pair.prefix);
|
||||
generateExtraDdl(subPath, pair.platform, request.isTablePartitioning());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +318,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return generateDiff(request);
|
||||
}
|
||||
|
||||
} catch (UnknownResourcePathException e) {
|
||||
logError("ERROR - " + e.getMessage());
|
||||
logError("Check the working directory or change dbMigration.setPathToResources() value?");
|
||||
return null;
|
||||
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
@@ -321,7 +340,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
setDefaults();
|
||||
try {
|
||||
return createRequest().getPendingDrops();
|
||||
return createRequest(false).getPendingDrops();
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
@@ -375,8 +394,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
|
||||
|
||||
String fullName = repeatableMigrationName(script.isInit(), script.getName());
|
||||
|
||||
logger.info("writing repeatable script {}", fullName);
|
||||
logger.debug("writing repeatable script {}", fullName);
|
||||
|
||||
File file = new File(migrationDir, fullName);
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
@@ -385,6 +403,30 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogToSystemOut(boolean logToSystemOut) {
|
||||
this.logToSystemOut = logToSystemOut;
|
||||
}
|
||||
|
||||
private void logError(String message) {
|
||||
if (logToSystemOut) {
|
||||
System.out.println("DbMigration> " + message);
|
||||
} else {
|
||||
logger.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void logInfo(String message, Object value) {
|
||||
if (value != null) {
|
||||
message = String.format(message, value);
|
||||
}
|
||||
if (logToSystemOut) {
|
||||
System.out.println("DbMigration> " + message);
|
||||
} else {
|
||||
logger.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String repeatableMigrationName(boolean init, String scriptName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (init) {
|
||||
@@ -404,12 +446,12 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
logger.info("Pending un-applied drops in versions {}", pendingDrops);
|
||||
logInfo("Pending un-applied drops in versions %s", pendingDrops);
|
||||
}
|
||||
|
||||
Migration migration = request.createDiffMigration();
|
||||
if (migration == null) {
|
||||
logger.info("no changes detected - no migration written");
|
||||
logInfo("no changes detected - no migration written", null);
|
||||
return null;
|
||||
} else {
|
||||
// there were actually changes to write
|
||||
@@ -428,41 +470,57 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
|
||||
logInfo("... remaining pending un-applied drops in versions %s", pendingDrops);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
private Request createRequest() {
|
||||
return new Request();
|
||||
private Request createRequest(boolean dbinitMigration) {
|
||||
return new Request(dbinitMigration);
|
||||
}
|
||||
|
||||
private class Request {
|
||||
|
||||
final boolean dbinitMigration;
|
||||
final File migrationDir;
|
||||
final File modelDir;
|
||||
final MigrationModel migrationModel;
|
||||
final CurrentModel currentModel;
|
||||
final ModelContainer migrated;
|
||||
final ModelContainer current;
|
||||
|
||||
private Request() {
|
||||
this.migrationDir = getMigrationDirectory();
|
||||
this.modelDir = getModelDirectory(migrationDir);
|
||||
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
this.migrated = migrationModel.read();
|
||||
private Request(boolean dbinitMigration) {
|
||||
this.dbinitMigration = dbinitMigration;
|
||||
this.currentModel = new CurrentModel(server, constraintNaming);
|
||||
this.current = currentModel.read();
|
||||
this.migrationDir = getMigrationDirectory(dbinitMigration);
|
||||
if (dbinitMigration) {
|
||||
this.modelDir = null;
|
||||
this.migrated = new ModelContainer();
|
||||
} else {
|
||||
this.modelDir = getModelDirectory(migrationDir);
|
||||
MigrationModel migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
this.migrated = migrationModel.read(dbinitMigration);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isTablePartitioning() {
|
||||
return current.isTablePartitioning();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next migration version (based on existing migration versions).
|
||||
*/
|
||||
String nextVersion() {
|
||||
// always read the next version using the main migration directory (not dbinit)
|
||||
File migDirectory = getMigrationDirectory(false);
|
||||
File modelDir = getModelDirectory(migDirectory);
|
||||
return LastMigration.nextVersion(migDirectory, modelDir, dbinitMigration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration for the pending drops for a given version.
|
||||
*/
|
||||
public Migration migrationForPendingDrop(String pendingVersion) {
|
||||
Migration migrationForPendingDrop(String pendingVersion) {
|
||||
|
||||
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
|
||||
|
||||
@@ -481,7 +539,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Create and return the diff of the current model to the migration model.
|
||||
*/
|
||||
public Migration createDiffMigration() {
|
||||
Migration createDiffMigration() {
|
||||
ModelDiff diff = new ModelDiff(migrated);
|
||||
diff.compareTo(current);
|
||||
return diff.isEmpty() ? null : diff.getMigration();
|
||||
@@ -490,11 +548,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
private String generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
|
||||
|
||||
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
|
||||
String fullVersion = getFullVersion(request.nextVersion(), dropsFor);
|
||||
|
||||
logger.info("generating migration:{}", fullVersion);
|
||||
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
logger.warn("migration already exists, not generating DDL");
|
||||
logInfo("generating migration:%s", fullVersion);
|
||||
if (!request.dbinitMigration && !writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
logError("migration already exists, not generating DDL");
|
||||
return null;
|
||||
} else {
|
||||
if (!platforms.isEmpty()) {
|
||||
@@ -528,11 +586,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* <p>
|
||||
* The full version can contain a comment suffix after a "__" double underscore.
|
||||
*/
|
||||
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
|
||||
private String getFullVersion(String nextVersion, String dropsFor) {
|
||||
|
||||
String version = getVersion();
|
||||
if (version == null) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
version = (nextVersion != null) ? nextVersion : initialVersion;
|
||||
}
|
||||
|
||||
String fullVersion = migrationConfig.getApplyPrefix() + version;
|
||||
@@ -559,7 +617,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
private void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
@@ -576,7 +634,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
private boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
|
||||
String modelFile = fullVersion + migrationConfig.getModelSuffix();
|
||||
File file = new File(resourcePath, modelFile);
|
||||
@@ -592,14 +650,13 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Set default server and platform if necessary.
|
||||
*/
|
||||
protected void setDefaults() {
|
||||
private void setDefaults() {
|
||||
if (server == null) {
|
||||
setServer(Ebean.getDefaultServer());
|
||||
}
|
||||
if (vanillaPlatform || databasePlatform == null) {
|
||||
// not explicitly set so use the platform of the server
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.trace("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
if (migrationConfig != null) {
|
||||
if (strictMode != null) {
|
||||
@@ -671,20 +728,31 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the main migration directory.
|
||||
*/
|
||||
File getMigrationDirectory() {
|
||||
return getMigrationDirectory(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getMigrationDirectory() {
|
||||
File getMigrationDirectory(boolean dbinitMigration) {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
String resourcePath = migrationConfig.getMigrationPath();
|
||||
if (!resourceRootDir.exists()) {
|
||||
String msg = String.format("Error - path to resources %s does not exist. Absolute path is %s", pathToResources, resourceRootDir.getAbsolutePath());
|
||||
throw new UnknownResourcePathException(msg);
|
||||
}
|
||||
String resourcePath = migrationConfig.getMigrationPath(dbinitMigration);
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/model
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
if (!path.exists()) {
|
||||
if (!path.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration directory exists at {}", path.getAbsolutePath());
|
||||
logInfo("Warning - Unable to ensure migration directory exists at %s", path.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return path;
|
||||
@@ -693,14 +761,14 @@ public class DefaultDbMigration implements DbMigration {
|
||||
/**
|
||||
* Return the model directory (relative to the migration directory).
|
||||
*/
|
||||
protected File getModelDirectory(File migrationDirectory) {
|
||||
private File getModelDirectory(File migrationDirectory) {
|
||||
String modelPath = migrationConfig.getModelPath();
|
||||
if (modelPath == null || modelPath.isEmpty()) {
|
||||
return migrationDirectory;
|
||||
}
|
||||
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
|
||||
if (!modelDir.exists() && !modelDir.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
|
||||
logInfo("Warning - Unable to ensure migration model directory exists at %s", modelDir.getAbsolutePath());
|
||||
}
|
||||
return modelDir;
|
||||
}
|
||||
@@ -746,19 +814,19 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* Holds a platform and prefix. Used to generate multiple platform specific DDL
|
||||
* for a single migration.
|
||||
*/
|
||||
public static class Pair {
|
||||
static class Pair {
|
||||
|
||||
/**
|
||||
* The platform to generate the DDL for.
|
||||
*/
|
||||
public final DatabasePlatform platform;
|
||||
final DatabasePlatform platform;
|
||||
|
||||
/**
|
||||
* A prefix included into the file/resource names indicating the platform.
|
||||
*/
|
||||
public final String prefix;
|
||||
final String prefix;
|
||||
|
||||
public Pair(DatabasePlatform platform, String prefix) {
|
||||
Pair(DatabasePlatform platform, String prefix) {
|
||||
this.platform = platform;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebeaninternal.dbmigration.model.MigrationVersion;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility to determine the last sql migration version and next version.
|
||||
*/
|
||||
class LastMigration {
|
||||
|
||||
private static final String SQL = ".sql";
|
||||
|
||||
private static final String MODEL_XML = ".model.xml";
|
||||
|
||||
/**
|
||||
* Return the next migation version given the migration directory.
|
||||
*/
|
||||
static String nextVersion(File migDir, File modelDir, boolean dbinitMigration) {
|
||||
|
||||
String last = lastVersion(migDir, modelDir);
|
||||
if (last == null) {
|
||||
return null;
|
||||
}
|
||||
return (dbinitMigration) ? last : MigrationVersion.parse(last).nextVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last migation version given the migration directory.
|
||||
*/
|
||||
static String lastVersion(File migDirectory, File modelDir) {
|
||||
|
||||
List<String> fileNames = new ArrayList<>();
|
||||
|
||||
File[] sqlFiles = migDirectory.listFiles(pathname -> includeSqlFile(pathname.getName().toLowerCase()));
|
||||
if (sqlFiles != null) {
|
||||
for (File file : sqlFiles) {
|
||||
fileNames.add(trim(file.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modelDir != null) {
|
||||
File[] xmlFiles = modelDir.listFiles(pathname -> includeModelFile(pathname.getName().toLowerCase()));
|
||||
if (xmlFiles != null) {
|
||||
for (File file : xmlFiles) {
|
||||
fileNames.add(trim(file.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(fileNames);
|
||||
if (!fileNames.isEmpty()) {
|
||||
return fileNames.get(fileNames.size() - 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean includeSqlFile(String lowerFileName) {
|
||||
if (lowerFileName.startsWith("r") || lowerFileName.startsWith("i") || !lowerFileName.endsWith(SQL)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean includeModelFile(String lowerFileName) {
|
||||
return lowerFileName.endsWith(MODEL_XML);
|
||||
}
|
||||
|
||||
private static String trim(String name) {
|
||||
name = name.toLowerCase();
|
||||
char c = name.charAt(0);
|
||||
if (c == 'v') {
|
||||
name = name.substring(1);
|
||||
}
|
||||
int p = name.indexOf("__");
|
||||
if (p > -1) {
|
||||
name = name.substring(0, p);
|
||||
}
|
||||
if (name.endsWith(SQL)) {
|
||||
name = name.substring(0, name.length() - 4);
|
||||
}
|
||||
if (name.endsWith(MODEL_XML)) {
|
||||
name = name.substring(0, name.length() - 10);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
/**
|
||||
* Exception when db migration resource path does not exist.
|
||||
* <p>
|
||||
* Typically the working directory or pathToResources is incorrect.
|
||||
* </p>
|
||||
*/
|
||||
public class UnknownResourcePathException extends RuntimeException {
|
||||
|
||||
public UnknownResourcePathException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
|
||||
|
||||
public H2HistoryDdl() {
|
||||
this.sysPeriodType = "timestamp";
|
||||
this.currentTimestamp = "now()";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HanaColumnStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaColumnStoreDdl(DatabasePlatform platform) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.config.PropertiesWrapper;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -12,6 +9,9 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class HanaTableDdl extends BaseTableDdl {
|
||||
|
||||
private final HanaHistoryDdl historyDdl;
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
@@ -78,7 +77,7 @@ public class PlatformDdl {
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
|
||||
protected String alterColumn = "alter column";
|
||||
|
||||
|
||||
protected String alterColumnSuffix = "";
|
||||
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
@@ -86,7 +85,7 @@ public class PlatformDdl {
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
|
||||
protected String addColumnSuffix = "";
|
||||
|
||||
protected String columnSetType = "";
|
||||
@@ -100,11 +99,11 @@ public class PlatformDdl {
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
|
||||
protected String createTable = "create table";
|
||||
|
||||
|
||||
protected String dropColumn = "drop column";
|
||||
|
||||
|
||||
protected String dropColumnSuffix = "";
|
||||
|
||||
/**
|
||||
@@ -674,12 +673,12 @@ public class PlatformDdl {
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the database-specific "create table" command prefix. For HANA this is
|
||||
* either "create column table" or "create row table", for all other databases
|
||||
* it is "create table".
|
||||
*
|
||||
*
|
||||
* @return The "create table" command prefix
|
||||
*/
|
||||
public String getCreateTableCommandPrefix() {
|
||||
|
||||
@@ -31,14 +31,16 @@ public class MigrationModel {
|
||||
/**
|
||||
* Read all the migrations returning the model with all
|
||||
* the migrations applied in version order.
|
||||
*
|
||||
* @param dbinitMigration If true we don't apply model changes, migration is from scratch.
|
||||
*/
|
||||
public ModelContainer read() {
|
||||
public ModelContainer read(boolean dbinitMigration) {
|
||||
|
||||
readMigrations();
|
||||
readMigrations(dbinitMigration);
|
||||
return model;
|
||||
}
|
||||
|
||||
private void readMigrations() {
|
||||
private void readMigrations(boolean dbinitMigration) {
|
||||
|
||||
// find all the migration xml files
|
||||
File[] xmlFiles = modelDirectory.listFiles(pathname -> pathname.getName().toLowerCase().endsWith(modelSuffix));
|
||||
@@ -53,9 +55,11 @@ public class MigrationModel {
|
||||
// sort into version order before applying
|
||||
Collections.sort(resources);
|
||||
|
||||
for (MigrationResource migrationResource : resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read(), migrationResource.getVersion());
|
||||
if (!dbinitMigration) {
|
||||
for (MigrationResource migrationResource : resources) {
|
||||
logger.debug("read {}", migrationResource);
|
||||
model.apply(migrationResource.read(), migrationResource.getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
// remember the last version
|
||||
|
||||
+5
-1
@@ -147,7 +147,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
@Override
|
||||
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
|
||||
|
||||
visitScalar(p);
|
||||
if (p instanceof BeanPropertyAssocOne) {
|
||||
visitOneImported((BeanPropertyAssocOne)p);
|
||||
} else {
|
||||
visitScalar(p);
|
||||
}
|
||||
if (embedded.isId()) {
|
||||
// compound primary key
|
||||
lastColumn.setPrimaryKey(true);
|
||||
|
||||
@@ -10,14 +10,14 @@ import java.util.Map;
|
||||
class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final Object id;
|
||||
private final String key;
|
||||
private final Map<String, Object> changes;
|
||||
private final boolean updateNaturalKey;
|
||||
private final long version;
|
||||
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
this.desc = desc;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.changes = changes;
|
||||
this.updateNaturalKey = updateNaturalKey;
|
||||
this.version = version;
|
||||
@@ -25,6 +25,6 @@ class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
desc.cacheApplyBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
desc.cacheApplyBeanUpdate(key, changes, updateNaturalKey, version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
class CacheChangeNaturalKeyPut implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final Object id;
|
||||
private final Object newKey;
|
||||
private final String key;
|
||||
private final String newKey;
|
||||
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, String key, String newKey) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.newKey = newKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheNaturalKeyPut(id, newKey);
|
||||
descriptor.cacheNaturalKeyPut(key, newKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,16 +152,16 @@ public class CacheChangeSet {
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
touchedTables.add(desc.getBaseTable());
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
entries.add(new CacheChangeBeanUpdate(desc, key, changes, updateNaturalKey, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a natural key.
|
||||
*/
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, String key, String val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, key, val));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
import io.ebean.annotation.CacheBeanTuning;
|
||||
import io.ebean.annotation.CacheQueryTuning;
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
@@ -123,11 +124,15 @@ class DefaultCacheHolder {
|
||||
}
|
||||
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
|
||||
Cache cache = cls.getAnnotation(Cache.class);
|
||||
boolean nearCache = (cache != null && cache.nearCache());
|
||||
|
||||
CacheBeanTuning tuning = cls.getAnnotation(CacheBeanTuning.class);
|
||||
if (tuning != null) {
|
||||
return new ServerCacheOptions(tuning).applyDefaults(beanDefault);
|
||||
return new ServerCacheOptions(nearCache, tuning).applyDefaults(beanDefault);
|
||||
}
|
||||
return beanDefault.copy();
|
||||
return beanDefault.copy(nearCache);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ClusterManager implements ServerLookup {
|
||||
|
||||
public ClusterManager(ContainerConfig config) {
|
||||
ClusterBroadcastFactory factory = createFactory();
|
||||
if (factory != null) {
|
||||
if (factory != null && config.isActive()) {
|
||||
broadcast = factory.create(this, config);
|
||||
} else {
|
||||
broadcast = null;
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.ScriptRunner;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.migration.runner.ScriptTransform;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.util.UrlHelper;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
@@ -63,7 +64,7 @@ class DScriptRunner implements ScriptRunner {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = resource.openStream()) {
|
||||
try (InputStream inputStream = UrlHelper.openNoCache(resource)) {
|
||||
return readContent(new InputStreamReader(inputStream));
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.lib.DaemonScheduleThreadPool;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -64,6 +66,46 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit) {
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
return schedulePool.schedule(r, delay, unit);
|
||||
} else {
|
||||
return schedulePool.schedule(() -> {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit) {
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
return schedulePool.schedule(c, delay, unit);
|
||||
} else {
|
||||
return schedulePool.schedule(new Callable<V>() {
|
||||
@Override
|
||||
public V call() throws Exception {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
return c.call();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
pool.shutdown();
|
||||
|
||||
@@ -88,4 +88,10 @@ public class DefaultBeanState implements BeanState {
|
||||
public void resetForInsert() {
|
||||
intercept.setNew();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Exception> getLoadErrors() {
|
||||
return intercept.getLoadErrors();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import io.ebean.meta.MetaOrmQueryNode;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,6 +25,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
|
||||
return server.collectQueryPlans(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
server.visitMetrics(visitor);
|
||||
|
||||
@@ -54,13 +54,16 @@ import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.LoadBeanRequest;
|
||||
import io.ebeaninternal.api.LoadManyRequest;
|
||||
import io.ebeaninternal.api.ScopedTransaction;
|
||||
@@ -120,6 +123,8 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -1358,6 +1363,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return !ids.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean exists(SpiQuery<?> ormQuery, SpiTransaction transaction) {
|
||||
|
||||
ormQuery.setMaxRows(1);
|
||||
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, ormQuery, transaction);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
List<Object> ids = request.findIds();
|
||||
return !ids.isEmpty();
|
||||
|
||||
} finally {
|
||||
request.endTransIfRequired();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
|
||||
|
||||
@@ -1439,10 +1459,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
public <T> FutureRowCount<T> findFutureRowCount(Query<T> q, Transaction t) {
|
||||
return findFutureCount(q, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> FutureIds<T> findFutureIds(Query<T> query, Transaction t) {
|
||||
|
||||
@@ -2335,10 +2351,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
|
||||
if (timeMicros > slowQueryMicros) {
|
||||
if (slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
if (timeMicros > slowQueryMicros && slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2425,4 +2439,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
visitor.visitEnd();
|
||||
}
|
||||
|
||||
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = getDataSource().getConnection();
|
||||
request.setConnection(connection);
|
||||
beanDescriptorManager.collectQueryPlans(request);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
} finally {
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
return request.getPlans();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,14 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlUpdate setParams(Object... values) {
|
||||
for (Object value : values) {
|
||||
setParameter(++addPos, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlUpdate setNextParameter(Object value) {
|
||||
setParameter(++addPos, value);
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
@@ -87,7 +88,6 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
|
||||
import io.ebeanservice.docstore.api.DocStoreIntegration;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
|
||||
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantCatalogProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantSchemaProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -118,7 +118,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
|
||||
@Override
|
||||
public boolean isDeleteByStatement() {
|
||||
return beanDescriptor.isDeleteByStatement();
|
||||
if (!transaction.isPersistCascade() || beanDescriptor.isDeleteByStatement()) {
|
||||
// plain delete by query
|
||||
return true;
|
||||
} else {
|
||||
// delete by ids due to cascading delete needs
|
||||
queryPlanKey = query.setDeleteByIdsPlan();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1405,6 +1405,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
String key = beanDescriptor.cacheKey(idValue);
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
EntityBean bean = getEntityBean();
|
||||
boolean[] dirtyProperties = getDirtyProperties();
|
||||
@@ -1417,7 +1419,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(beanDescriptor, idValue, val);
|
||||
changeSet.addNaturalKeyPut(beanDescriptor, key, val.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1428,7 +1430,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changes.putAll(collectionChanges);
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
|
||||
changeSet.addBeanUpdate(beanDescriptor, key, changes, updateNaturalKey, getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.core.bootup;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.util.UrlHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -59,7 +60,7 @@ class ManifestReader {
|
||||
try {
|
||||
Enumeration<URL> resources = classLoader.getResources(resourcePath);
|
||||
while (resources.hasMoreElements()) {
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
try (InputStream is = UrlHelper.openNoCache(resources.nextElement())) {
|
||||
read(new Manifest(is));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -16,9 +17,16 @@ abstract class AssocOneHelp {
|
||||
|
||||
protected final BeanDescriptor<?> target;
|
||||
|
||||
private final String path;
|
||||
|
||||
AssocOneHelp(BeanPropertyAssocOne<?> property) {
|
||||
this(property, null);
|
||||
}
|
||||
|
||||
AssocOneHelp(BeanPropertyAssocOne<?> property, String embeddedPrefix) {
|
||||
this.property = property;
|
||||
this.target = property.targetDescriptor;
|
||||
this.path = (embeddedPrefix == null) ? property.name : embeddedPrefix + "." + property.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +36,24 @@ abstract class AssocOneHelp {
|
||||
property.targetIdBinder.loadIgnore(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the property.
|
||||
*/
|
||||
Object read(DataReader reader) throws SQLException {
|
||||
return property.read(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the property setting value into the bean.
|
||||
*/
|
||||
Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
Object val = read(reader);
|
||||
if (bean != null) {
|
||||
property.setValue(bean, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return the bean.
|
||||
*/
|
||||
@@ -48,7 +74,7 @@ abstract class AssocOneHelp {
|
||||
boolean disableLazyLoading = ctx.isDisableLazyLoading();
|
||||
Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
|
||||
if (!disableLazyLoading) {
|
||||
ctx.register(property.name, ((EntityBean) ref)._ebean_getIntercept());
|
||||
ctx.register(path, ((EntityBean) ref)._ebean_getIntercept());
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
@@ -9,14 +10,41 @@ import java.sql.SQLException;
|
||||
*/
|
||||
final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
|
||||
public AssocOneHelpEmbedded(BeanPropertyAssocOne<?> property) {
|
||||
AssocOneHelpEmbedded(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
void loadIgnore(DbReadContext ctx) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].loadIgnore(ctx);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
property.loadIgnore(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
Object dbVal = read(reader);
|
||||
if (bean != null) {
|
||||
property.setValue(bean, dbVal);
|
||||
}
|
||||
return dbVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
Object read(DataReader reader) throws SQLException {
|
||||
|
||||
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
|
||||
boolean notNull = false;
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
Object value = property.readSet(reader, embeddedBean);
|
||||
if (value != null) {
|
||||
notNull = true;
|
||||
}
|
||||
}
|
||||
if (notNull) {
|
||||
return embeddedBean;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +67,8 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
|
||||
|
||||
boolean notNull = false;
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
Object value = property.embeddedProps[i].readSet(ctx, embeddedBean);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
Object value = property.readSet(ctx, embeddedBean);
|
||||
if (value != null) {
|
||||
notNull = true;
|
||||
}
|
||||
@@ -55,8 +83,8 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
|
||||
|
||||
@Override
|
||||
void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
for (int i = 0; i < property.embeddedProps.length; i++) {
|
||||
property.embeddedProps[i].appendSelect(ctx, subQuery);
|
||||
for (BeanProperty property : property.embeddedProps) {
|
||||
property.appendSelect(ctx, subQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ package io.ebeaninternal.server.deploy;
|
||||
*/
|
||||
class AssocOneHelpRefSimple extends AssocOneHelp {
|
||||
|
||||
AssocOneHelpRefSimple(BeanPropertyAssocOne<?> property) {
|
||||
super(property);
|
||||
AssocOneHelpRefSimple(BeanPropertyAssocOne<?> property, String embeddedPrefix) {
|
||||
super(property, embeddedPrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.CQueryCollectionAdd;
|
||||
|
||||
|
||||
/**
|
||||
@@ -10,10 +12,10 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
public class BeanCollectionHelpFactory {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static final BeanListHelp LIST_HELP = new BeanListHelp();
|
||||
private static final BeanListHelp LIST_HELP = new BeanListHelp();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static final BeanSetHelp SET_HELP = new BeanSetHelp();
|
||||
private static final BeanSetHelp SET_HELP = new BeanSetHelp();
|
||||
|
||||
/**
|
||||
* Create the helper based on the many property.
|
||||
@@ -36,7 +38,7 @@ public class BeanCollectionHelpFactory {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> BeanCollectionHelp<T> create(SpiQuery.Type manyType, OrmQueryRequest<T> request) {
|
||||
public static <T> CQueryCollectionAdd<T> create(SpiQuery.Type manyType, OrmQueryRequest<T> request) {
|
||||
|
||||
if (manyType == SpiQuery.Type.LIST) {
|
||||
return LIST_HELP;
|
||||
@@ -44,10 +46,13 @@ public class BeanCollectionHelpFactory {
|
||||
} else if (manyType == SpiQuery.Type.SET) {
|
||||
return SET_HELP;
|
||||
|
||||
} else {
|
||||
} else if (manyType == SpiQuery.Type.MAP) {
|
||||
BeanDescriptor<T> target = request.getBeanDescriptor();
|
||||
String mapKey = request.getQuery().getMapKey();
|
||||
return new BeanMapHelp<>(target, mapKey);
|
||||
ElPropertyValue elProperty = target.getElGetValue(request.getQuery().getMapKey());
|
||||
return new BeanMapQueryHelp<>(elProperty);
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
@@ -30,6 +29,7 @@ import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanDocType;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
@@ -724,16 +724,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
inheritInfo.setDescriptor(this);
|
||||
}
|
||||
|
||||
if (isEmbedded()) {
|
||||
// initialise all the properties
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
prop.initialise(initContext);
|
||||
}
|
||||
} else {
|
||||
// initialise just the Id properties
|
||||
if (idProperty != null) {
|
||||
idProperty.initialise(initContext);
|
||||
}
|
||||
// initialise just the Id property only
|
||||
if (idProperty != null) {
|
||||
idProperty.initialise(initContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,14 +752,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEmbedded()) {
|
||||
// initialise all the non-id properties
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
if (!prop.isId()) {
|
||||
prop.initialise(initContext);
|
||||
}
|
||||
prop.registerColumn(this, null);
|
||||
// initialise all the non-id properties
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
if (!prop.isId()) {
|
||||
prop.initialise(initContext);
|
||||
}
|
||||
prop.registerColumn(this, null);
|
||||
}
|
||||
|
||||
if (unidirectional != null) {
|
||||
@@ -1487,30 +1478,33 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* Return a bean from the bean cache (or null).
|
||||
*/
|
||||
public T cacheBeanGet(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheGet(id, readOnly, context);
|
||||
return cacheHelp.beanCacheGet(cacheKey(id), readOnly, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a collection of beans from the cache given the ids.
|
||||
*/
|
||||
public void cacheApplyInvalidate(Collection<Object> ids) {
|
||||
cacheHelp.beanCacheApplyInvalidate(ids);
|
||||
List<String> keys = new ArrayList<>(ids.size());
|
||||
for (Object id : ids) {
|
||||
keys.add(cacheKey(id));
|
||||
}
|
||||
cacheHelp.beanCacheApplyInvalidate(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit the bean cache trying to load a list/batch of entities.
|
||||
* Return the set of entities that were successfully loaded from L2 cache.
|
||||
*/
|
||||
public Set<EntityBeanIntercept> cacheBeanLoadAll(List<EntityBeanIntercept> list, PersistenceContext persistenceContext, int lazyLoadProperty, String propertyName) {
|
||||
return cacheHelp.beanCacheLoadAll(list, persistenceContext, lazyLoadProperty, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, id, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBeanIntercept ebi, PersistenceContext context) {
|
||||
EntityBean bean = ebi.getOwner();
|
||||
Object id = getId(bean);
|
||||
return cacheBeanLoad(bean, ebi, id, context);
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1520,8 +1514,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return cacheHelp.naturalKeyLookup(context, keys);
|
||||
}
|
||||
|
||||
public void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
cacheHelp.cacheNaturalKeyPut(id, newKey);
|
||||
public void cacheNaturalKeyPut(String key, String newKey) {
|
||||
cacheHelp.cacheNaturalKeyPut(key, newKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1569,8 +1563,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Apply the update to the cache.
|
||||
*/
|
||||
public void cacheApplyBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
public void cacheApplyBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
cacheHelp.cacheBeanUpdate(key, changes, updateNaturalKey, version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1652,6 +1646,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return new DeployUpdateParser(this).parse(ormUpdateStatement);
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
if (request.includeLabel(queryPlan.getLabel())) {
|
||||
queryPlan.collectQueryPlan(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
|
||||
*/
|
||||
@@ -1981,7 +1983,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) {
|
||||
|
||||
if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) {
|
||||
CachedBeanData d = cacheHelp.beanCacheGetData(id);
|
||||
CachedBeanData d = cacheHelp.beanCacheGetData(cacheKey(id));
|
||||
if (d != null) {
|
||||
Object shareableBean = d.getSharableBean();
|
||||
if (shareableBean != null) {
|
||||
@@ -2262,6 +2264,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return (idProperty == null) ? null : idProperty.getValueIntercept(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache key for the given bean (based on id value).
|
||||
*/
|
||||
public String cacheKeyForBean(EntityBean bean) {
|
||||
return cacheKey(idProperty.getValue(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache key for the given id value.
|
||||
*/
|
||||
public String cacheKey(Object id) {
|
||||
return idBinder.cacheKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beanId(Object bean) {
|
||||
return getId((EntityBean) bean);
|
||||
@@ -2570,9 +2586,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Return a 'dynamic property' used to read a formula.
|
||||
*/
|
||||
private STreeProperty findSqlTreeFormula(String formulaExpression) {
|
||||
|
||||
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build());
|
||||
private STreeProperty findSqlTreeFormula(String formula, String path) {
|
||||
String key = formula + "-" + path;
|
||||
return dynamicProperty.computeIfAbsent(key, (fullKey) -> new FormulaPropertyPath(this, formula, path).build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2581,9 +2597,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* The property can be a dynamic formula or a well known bean property.
|
||||
*/
|
||||
@Override
|
||||
public STreeProperty findPropertyWithDynamic(String propName) {
|
||||
public STreeProperty findPropertyWithDynamic(String propName, String path) {
|
||||
if (propName.indexOf('(') > -1) {
|
||||
return findSqlTreeFormula(propName);
|
||||
return findSqlTreeFormula(propName, path);
|
||||
}
|
||||
return _findBeanProperty(propName);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -282,7 +283,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
if (many.isElementCollection()) {
|
||||
// held as part of the bean cache so skip
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedManyIds entry = manyPropGet(parentId, many.getName());
|
||||
@@ -317,7 +318,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
try {
|
||||
// add as JSON to bean cache
|
||||
String asJson = many.jsonWriteCollection(details);
|
||||
Map<String,Object> changes = new HashMap<>();
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
changes.put(many.getName(), asJson);
|
||||
|
||||
CachedBeanData newData = data.update(changes, data.getVersion());
|
||||
@@ -475,7 +476,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private Class<?> theClassOf(Collection<EntityBean> beans) {
|
||||
if (beans instanceof List) {
|
||||
return ((List<?>)beans).get(0).getClass();
|
||||
return ((List<?>) beans).get(0).getClass();
|
||||
}
|
||||
return beans.iterator().next().getClass();
|
||||
}
|
||||
@@ -494,20 +495,20 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
void beanCachePutAllDirect(Collection<EntityBean> beans) {
|
||||
|
||||
Map<Object,Object> natKeys = null;
|
||||
Map<Object, Object> natKeys = null;
|
||||
if (naturalKey != null) {
|
||||
natKeys = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
Map<Object,Object> map = new LinkedHashMap<>();
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
for (EntityBean bean : beans) {
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
Object id = desc.getId(bean);
|
||||
map.put(id, beanData);
|
||||
String key = desc.cacheKeyForBean(bean);
|
||||
map.put(key, beanData);
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
natKeys.put(naturalKey, id);
|
||||
natKeys.put(naturalKey, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,32 +526,33 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
String key = desc.cacheKeyForBean(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, id, beanData);
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, key, beanData);
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
getBeanCache().put(key, beanData);
|
||||
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
String naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, key);
|
||||
}
|
||||
naturalKeyCache.put(naturalKey, id);
|
||||
naturalKeyCache.put(naturalKey, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object calculateNaturalKey(CachedBeanData beanData) {
|
||||
private String calculateNaturalKey(CachedBeanData beanData) {
|
||||
if (naturalKey.length == 1) {
|
||||
return beanData.getData(naturalKey[0]);
|
||||
Object data = beanData.getData(naturalKey[0]);
|
||||
return (data == null) ? null : data.toString();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
@@ -563,12 +565,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
CachedBeanData beanCacheGetData(String key) {
|
||||
return (CachedBeanData) getBeanCache().get(key);
|
||||
}
|
||||
|
||||
T beanCacheGet(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
T bean = beanCacheGetInternal(id, readOnly, context);
|
||||
T beanCacheGet(String key, Boolean readOnly, PersistenceContext context) {
|
||||
T bean = beanCacheGetInternal(key, readOnly, context);
|
||||
if (bean != null) {
|
||||
setupContext(bean, context);
|
||||
}
|
||||
@@ -578,19 +580,19 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Return a bean from the bean cache.
|
||||
*/
|
||||
private T beanCacheGetInternal(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
|
||||
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(id);
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
|
||||
if (data == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, id);
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, key);
|
||||
}
|
||||
return convertToBean(id, readOnly, context, data);
|
||||
return convertToBean(key, readOnly, context, data);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -686,12 +688,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
void beanCacheApplyInvalidate(Collection<Object> ids) {
|
||||
void beanCacheApplyInvalidate(Collection<String> keys) {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, ids);
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, keys);
|
||||
}
|
||||
beanCache.removeAll(new HashSet<>(ids));
|
||||
beanCache.removeAll(new HashSet<>(keys));
|
||||
}
|
||||
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
|
||||
imported.cacheClear();
|
||||
@@ -699,28 +701,77 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
* Load a batch of entities from L2 bean cache checking the lazy loaded property is loaded.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
Set<EntityBeanIntercept> beanCacheLoadAll(List<EntityBeanIntercept> list, PersistenceContext context, int lazyLoadProperty, String propertyName) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id);
|
||||
Map<Object, EntityBeanIntercept> ebis = new HashMap<>();
|
||||
for (EntityBeanIntercept ebi : list) {
|
||||
ebis.put(desc.cacheKeyForBean(ebi.getOwner()), ebi);
|
||||
}
|
||||
|
||||
|
||||
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD ALL {}({}) - got hits ({})", cacheName, ebis.keySet(), hits.size());
|
||||
}
|
||||
|
||||
Set<EntityBeanIntercept> loaded = new HashSet<>();
|
||||
|
||||
Iterator<Map.Entry<Object, Object>> iterator = hits.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Object, Object> hit = iterator.next();
|
||||
|
||||
Object key = hit.getKey();
|
||||
EntityBeanIntercept ebi = ebis.remove(key);
|
||||
CachedBeanData cacheData = (CachedBeanData) hit.getValue();
|
||||
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(propertyName)) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, key, propertyName);
|
||||
}
|
||||
iterator.remove();
|
||||
|
||||
} else {
|
||||
CachedBeanDataToBean.load(desc, ebi.getOwner(), cacheData, context);
|
||||
loaded.add(ebi);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ebis.isEmpty() && beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, ebis.keySet());
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the single bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(key);
|
||||
if (cacheData == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, id);
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(ebi.getLazyLoadProperty())) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, id, ebi.getLazyLoadProperty());
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, key, ebi.getLazyLoadProperty());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, cacheData, context);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, id);
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -736,7 +787,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
void persistDeleteIds(Collection<Object> ids, CacheChangeSet changeSet) {
|
||||
if (invalidateQueryCache) {
|
||||
changeSet.addInvalidate(desc);
|
||||
changeSet.addInvalidate(desc);
|
||||
} else {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
@@ -833,42 +884,42 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
void cacheNaturalKeyPut(String key, String newKey) {
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.put(newKey, id);
|
||||
naturalKeyCache.put(newKey, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
void cacheBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(id);
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(key);
|
||||
if (existingData != null) {
|
||||
long currentVersion = existingData.getVersion();
|
||||
if (version > 0 && version < currentVersion) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, id, currentVersion, version);
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, key, currentVersion, version);
|
||||
}
|
||||
cache.remove(id);
|
||||
cache.remove(key);
|
||||
} else {
|
||||
if (version == 0) {
|
||||
version = currentVersion;
|
||||
}
|
||||
CachedBeanData newData = existingData.update(changes, version);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, id, changes);
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, key, changes);
|
||||
}
|
||||
cache.put(id, newData);
|
||||
cache.put(key, newData);
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = calculateNaturalKey(existingData);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, key);
|
||||
}
|
||||
naturalKeyCache.remove(oldKey);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ class BeanDescriptorInitContext {
|
||||
private final Map<String, String> draftTables;
|
||||
private final String asOfViewSuffix;
|
||||
|
||||
private String embeddedPrefix;
|
||||
|
||||
BeanDescriptorInitContext(Map<String, String> withHistoryTables, Map<String, String> draftTables, String asOfViewSuffix) {
|
||||
this.withHistoryTables = withHistoryTables;
|
||||
this.draftTables = draftTables;
|
||||
@@ -29,4 +31,12 @@ class BeanDescriptorInitContext {
|
||||
void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) {
|
||||
draftTables.put(intersectionPublishTable, intersectionDraftTable);
|
||||
}
|
||||
|
||||
public void setEmbeddedPrefix(String embeddedPrefix) {
|
||||
this.embeddedPrefix = embeddedPrefix;
|
||||
}
|
||||
|
||||
public String getEmbeddedPrefix() {
|
||||
return embeddedPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import io.ebeaninternal.api.ConcurrencyMode;
|
||||
@@ -149,8 +150,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<>();
|
||||
|
||||
private final Map<Class<?>, BeanTable> beanTableMap = new HashMap<>();
|
||||
|
||||
private final Map<String, BeanDescriptor<?>> descMap = new HashMap<>();
|
||||
@@ -195,6 +194,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
private final int queryPlanTTLSeconds;
|
||||
|
||||
// temporary collections used during startup and then cleared
|
||||
|
||||
private Map<Class<?>, DeployBeanInfo<?>> deployInfoMap = new HashMap<>();
|
||||
private Set<Class<?>> embeddedIdTypes = new HashSet<>();
|
||||
private List<DeployBeanInfo<?>> embeddedBeans = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create for a given database dbConfig.
|
||||
*/
|
||||
@@ -365,7 +370,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
createListeners();
|
||||
readEntityDeploymentInitial();
|
||||
readXmlMapping(mappings);
|
||||
readEmbeddedDeployment();
|
||||
readEntityBeanTable();
|
||||
readEntityDeploymentAssociations();
|
||||
readInheritedIdGenerators();
|
||||
@@ -384,7 +388,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
|
||||
logStatus();
|
||||
|
||||
deployInfoMap.clear();
|
||||
// clear collections we no longer need
|
||||
embeddedIdTypes = null;
|
||||
embeddedBeans = null;
|
||||
deployInfoMap = null;
|
||||
|
||||
return asOfTableMap;
|
||||
@@ -591,8 +597,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
for (BeanDescriptor<?> d : descMap.values()) {
|
||||
d.initLast();
|
||||
if (!d.isEmbedded()) {
|
||||
BeanManager<?> m = beanManagerFactory.create(d);
|
||||
beanManagerMap.put(d.getFullName(), m);
|
||||
beanManagerMap.put(d.getFullName(), beanManagerFactory.create(d));
|
||||
checkForValidEmbeddedId(d);
|
||||
}
|
||||
}
|
||||
@@ -675,11 +680,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
logger.debug("Entities[{}]", entityBeanCount);
|
||||
}
|
||||
|
||||
private <T> BeanDescriptor<T> createEmbedded(Class<T> beanClass) {
|
||||
DeployBeanInfo<T> info = getDeploy(beanClass);
|
||||
return new BeanDescriptor<>(this, info.getDescriptor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean deploy info for the given class.
|
||||
*/
|
||||
@@ -688,24 +688,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return (DeployBeanInfo<T>) deployInfoMap.get(cls);
|
||||
}
|
||||
|
||||
private void registerBeanDescriptor(BeanDescriptor<?> desc) {
|
||||
private void registerBeanDescriptor(DeployBeanInfo<?> info) {
|
||||
BeanDescriptor desc = new BeanDescriptor<>(this, info.getDescriptor());
|
||||
descMap.put(desc.getBeanType().getName(), desc);
|
||||
if (desc.isDocStoreMapped()) {
|
||||
descQueueMap.put(desc.getDocStoreQueueId(), desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read deployment information for all the embedded beans.
|
||||
*/
|
||||
private void readEmbeddedDeployment() {
|
||||
|
||||
List<Class<?>> embeddedClasses = bootupClasses.getEmbeddables();
|
||||
for (Class<?> embeddedClass : embeddedClasses) {
|
||||
registerBeanDescriptor(createEmbedded(embeddedClass));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the initial deployment information for the entities.
|
||||
* <p>
|
||||
@@ -718,14 +708,31 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
for (Class<?> entityClass : bootupClasses.getEntities()) {
|
||||
DeployBeanInfo<?> info = createDeployBeanInfo(entityClass);
|
||||
deployInfoMap.put(entityClass, info);
|
||||
Class<?> embeddedIdType = info.getEmbeddedIdType();
|
||||
if (embeddedIdType != null){
|
||||
embeddedIdTypes.add(embeddedIdType);
|
||||
}
|
||||
}
|
||||
for (Class<?> entityClass : bootupClasses.getEmbeddables()) {
|
||||
DeployBeanInfo<?> info = createDeployBeanInfo(entityClass);
|
||||
readDeployAssociations(info);
|
||||
deployInfoMap.put(entityClass, info);
|
||||
if (embeddedIdTypes.contains(entityClass)) {
|
||||
// register embeddedId types early - scalar properties only
|
||||
// and needed for creating BeanTables (id properties)
|
||||
registerEmbeddedBean(info);
|
||||
} else {
|
||||
// delay register of other embedded beans until after
|
||||
// the BeanTables have been created to support ManyToOne
|
||||
embeddedBeans.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerEmbeddedBean(DeployBeanInfo<?> info) {
|
||||
readDeployAssociations(info);
|
||||
registerBeanDescriptor(info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the BeanTable information which has the base table and id.
|
||||
* <p>
|
||||
@@ -738,6 +745,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
BeanTable beanTable = createBeanTable(info);
|
||||
beanTableMap.put(beanTable.getBeanType(), beanTable);
|
||||
}
|
||||
|
||||
// register non-id embedded beans (after bean tables are created)
|
||||
for (DeployBeanInfo<?> info : embeddedBeans) {
|
||||
registerEmbeddedBean(info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -815,13 +827,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
secondaryPropsJoins(info);
|
||||
}
|
||||
|
||||
// Set inheritance info
|
||||
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
|
||||
setInheritanceInfo(info);
|
||||
}
|
||||
|
||||
for (DeployBeanInfo<?> info : deployInfoMap.values()) {
|
||||
registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor()));
|
||||
if (!info.isEmbedded()) {
|
||||
registerBeanDescriptor(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1688,6 +1701,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (BeanDescriptor<?> desc : immutableDescriptorList) {
|
||||
if (request.includeType(desc.getBeanType())) {
|
||||
desc.collectQueryPlans(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort the BeanDescriptors by name.
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ public class BeanEmbeddedMetaFactory {
|
||||
String columnPrefix = prop.getColumnPrefix();
|
||||
Map<String, String> propColMap = prop.getDeployEmbedded().getPropertyColumnMap();
|
||||
|
||||
BeanProperty[] sourceProperties = targetDesc.propertiesBaseScalar();
|
||||
BeanProperty[] sourceProperties = targetDesc.propertiesNonTransient();
|
||||
BeanProperty[] embeddedProperties = new BeanProperty[sourceProperties.length];
|
||||
|
||||
for (int i = 0; i < sourceProperties.length; i++) {
|
||||
@@ -45,7 +45,11 @@ public class BeanEmbeddedMetaFactory {
|
||||
}
|
||||
|
||||
BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn);
|
||||
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
|
||||
if (sourceProperties[i] instanceof BeanPropertyAssocOne) {
|
||||
embeddedProperties[i] = new BeanPropertyAssocOne((BeanPropertyAssocOne)sourceProperties[i], overrides);
|
||||
} else {
|
||||
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
|
||||
}
|
||||
}
|
||||
|
||||
return new BeanEmbeddedMeta(embeddedProperties);
|
||||
|
||||
@@ -56,11 +56,10 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
}
|
||||
}
|
||||
|
||||
public BeanFkeyProperty create(String expression, boolean containsMany) {
|
||||
public BeanFkeyProperty create(String expression, boolean pathContainsMany) {
|
||||
int len = expression.length() - name.length() - 1;
|
||||
String prefix = expression.substring(0, len);
|
||||
|
||||
return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder, containsMany);
|
||||
return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder, containsMany || pathContainsMany);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,16 +24,6 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
|
||||
private final String propertyName;
|
||||
private final BeanProperty beanProperty;
|
||||
|
||||
/**
|
||||
* When created for a given query that will return a map.
|
||||
*/
|
||||
BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
|
||||
this.targetDescriptor = targetDescriptor;
|
||||
this.beanProperty = targetDescriptor.getBeanProperty(mapKey);
|
||||
this.many = null;
|
||||
this.propertyName = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* When help is attached to a specific many property.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.common.BeanMap;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.CQueryCollectionAdd;
|
||||
|
||||
class BeanMapQueryHelp<T> implements CQueryCollectionAdd<T> {
|
||||
|
||||
private final ElPropertyValue elGetValue;
|
||||
|
||||
/**
|
||||
* Create for a findMap query.
|
||||
*/
|
||||
BeanMapQueryHelp(ElPropertyValue elGetValue) {
|
||||
this.elGetValue = elGetValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<T> createEmptyNoParent() {
|
||||
return new BeanMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(BeanCollection collection, EntityBean bean, boolean withCheck) {
|
||||
if (bean == null) {
|
||||
((BeanMap<?, ?>) collection).internalPutNull();
|
||||
} else {
|
||||
Object keyValue = elGetValue.pathGet(bean);
|
||||
BeanMap<?, ?> map = ((BeanMap<?, ?>) collection);
|
||||
map.internalPutWithCheck(keyValue, bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty;
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
import io.ebeaninternal.server.type.LocalEncryptedType;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.ScalarTypeBoolean;
|
||||
@@ -640,6 +641,23 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(DataReader reader) throws SQLException {
|
||||
return scalarType.read(reader);
|
||||
}
|
||||
|
||||
public Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
try {
|
||||
Object value = scalarType.read(reader);
|
||||
if (bean != null) {
|
||||
setValue(bean, value);
|
||||
}
|
||||
return value;
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
return scalarType.read(ctx.getDataReader());
|
||||
}
|
||||
@@ -809,6 +827,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value in String format (for bean cache key).
|
||||
*/
|
||||
public String format(Object value) {
|
||||
return scalarType.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value for this property from L2 cache entry and set it to the bean.
|
||||
* <p>
|
||||
|
||||
@@ -104,6 +104,22 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
|
||||
this.fetchPreference = deploy.getFetchPreference();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for ManyToOne inside Embeddable.
|
||||
*/
|
||||
public BeanPropertyAssoc(BeanPropertyAssoc source, BeanPropertyOverride override) {
|
||||
super(source, override);
|
||||
foreignKey = source.foreignKey;
|
||||
extraWhere = source.extraWhere;
|
||||
beanTable = source.beanTable;
|
||||
mappedBy = source.mappedBy;
|
||||
docStoreDoc = source.docStoreDoc;
|
||||
targetType = source.targetType;
|
||||
cascadeInfo = source.cascadeInfo;
|
||||
fetchPreference = source.fetchPreference;
|
||||
tableJoin = source.tableJoin.withOverrideColumn(override.getDbColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise post construction.
|
||||
*/
|
||||
|
||||
@@ -204,7 +204,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public void initialisePostTarget() {
|
||||
if (childMasterProperty != null) {
|
||||
BeanProperty masterId = childMasterProperty.getTargetDescriptor().getIdProperty();
|
||||
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
|
||||
if (masterId != null) { // in docstore only, the master-id may be not available
|
||||
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,8 +974,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
|
||||
try {
|
||||
String asJson = (String) cacheData;
|
||||
Object collection = jsonReadCollection(asJson);
|
||||
setValue(bean, collection);
|
||||
if (asJson != null && !asJson.isEmpty()) {
|
||||
Object collection = jsonReadCollection(asJson);
|
||||
setValue(bean, collection);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error setting value from L2 cache", e);
|
||||
}
|
||||
@@ -999,7 +1003,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public String jsonWriteCollection(Object value) throws IOException {
|
||||
StringWriter writer = new StringWriter(300);
|
||||
SpiJsonWriter ctx = descriptor.createJsonWriter(writer);
|
||||
help.jsonWrite(ctx, null, value, false);
|
||||
help.jsonWrite(ctx, null, value, true);
|
||||
ctx.flush();
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.STreePropertyAssocOne;
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
import io.ebeaninternal.server.type.ScalarDataReader;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
@@ -92,14 +93,35 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for ManyToOne inside Embeddable.
|
||||
*/
|
||||
public BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) {
|
||||
super(source, override);
|
||||
primaryKeyExport = source.primaryKeyExport;
|
||||
oneToOne = source.oneToOne;
|
||||
oneToOneExported = source.oneToOneExported;
|
||||
orphanRemoval = source.orphanRemoval;
|
||||
embeddedProps = null;
|
||||
embeddedPropsMap = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialise(BeanDescriptorInitContext initContext) {
|
||||
super.initialise(initContext);
|
||||
initialiseAssocOne();
|
||||
initialiseAssocOne(initContext.getEmbeddedPrefix());
|
||||
if (embedded) {
|
||||
// initialise ManyToOne importedId
|
||||
initContext.setEmbeddedPrefix(name);
|
||||
for (BeanProperty embeddedProp : embeddedProps) {
|
||||
embeddedProp.initialise(initContext);
|
||||
}
|
||||
initContext.setEmbeddedPrefix(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void initialiseAssocOne() {
|
||||
localHelp = createHelp(embedded, oneToOneExported);
|
||||
private void initialiseAssocOne(String embeddedPrefix) {
|
||||
localHelp = createHelp(embedded, oneToOneExported, embeddedPrefix);
|
||||
|
||||
if (!isTransient) {
|
||||
//noinspection StatementWithEmptyBody
|
||||
@@ -412,6 +434,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Object value) {
|
||||
return targetDescriptor.getIdBinder().cacheKey(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
|
||||
if (cacheData == null) {
|
||||
@@ -434,8 +461,8 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> getIdScalarType() {
|
||||
return targetDescriptor.getIdProperty().getScalarType();
|
||||
public ScalarDataReader<?> getIdReader() {
|
||||
return targetDescriptor.getIdProperty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -583,18 +610,23 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readSet(DataReader reader, EntityBean bean) throws SQLException {
|
||||
return localHelp.readSet(reader, bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(DataReader reader) throws SQLException {
|
||||
return localHelp.read(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
|
||||
return localHelp.readSet(ctx, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the data from the resultSet effectively ignoring it and returning null.
|
||||
*/
|
||||
@Override
|
||||
public Object read(DbReadContext ctx) throws SQLException {
|
||||
// just read the resultSet incrementing the column index
|
||||
// pass in null for the bean so any data read is ignored
|
||||
return localHelp.read(ctx);
|
||||
}
|
||||
|
||||
@@ -657,7 +689,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
}
|
||||
|
||||
private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported) {
|
||||
private AssocOneHelp createHelp(boolean embedded, boolean oneToOneExported, String embeddedPrefix) {
|
||||
if (embedded) {
|
||||
return new AssocOneHelpEmbedded(this);
|
||||
} else if (oneToOneExported) {
|
||||
@@ -666,7 +698,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
if (targetInheritInfo != null) {
|
||||
return new AssocOneHelpRefInherit(this);
|
||||
} else {
|
||||
return new AssocOneHelpRefSimple(this);
|
||||
return new AssocOneHelpRefSimple(this, embeddedPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,4 +90,10 @@ public interface DbReadContext {
|
||||
* Return true if this request disables lazy loading.
|
||||
*/
|
||||
boolean isDisableLazyLoading();
|
||||
|
||||
/**
|
||||
* Handles a load error on given property.
|
||||
*/
|
||||
void handleLoadError(String fullName, Exception e);
|
||||
|
||||
}
|
||||
|
||||
+18
-7
@@ -1,9 +1,11 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Dynamic property based on aggregation (max, min, avg, count).
|
||||
@@ -37,19 +39,28 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(SqlBeanLoad sqlBeanLoad) {
|
||||
|
||||
public Object read(DataReader dataReader) throws SQLException {
|
||||
try {
|
||||
Object value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
|
||||
if (asTarget != null) {
|
||||
sqlBeanLoad.load(asTarget, value);
|
||||
}
|
||||
|
||||
return scalarType.read(dataReader);
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException("Error loading on " + fullName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(SqlBeanLoad sqlBeanLoad) {
|
||||
Object value;
|
||||
try {
|
||||
value = scalarType.read(sqlBeanLoad.ctx().getDataReader());
|
||||
} catch (Exception e) {
|
||||
sqlBeanLoad.ctx().handleLoadError(fullName, e);
|
||||
return;
|
||||
}
|
||||
if (asTarget != null) {
|
||||
sqlBeanLoad.load(asTarget, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
ctx.appendParseSelect(parsedFormula, alias);
|
||||
|
||||
@@ -20,15 +20,17 @@ class FormulaPropertyPath {
|
||||
|
||||
private final String internalExpression;
|
||||
|
||||
private final String path;
|
||||
|
||||
private boolean countDistinct;
|
||||
|
||||
private String cast;
|
||||
private String alias;
|
||||
|
||||
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula) {
|
||||
|
||||
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula, String path) {
|
||||
this.descriptor = descriptor;
|
||||
this.formula = formula;
|
||||
this.path = path;
|
||||
|
||||
int openBracket = formula.indexOf('(');
|
||||
int closeBracket = formula.lastIndexOf(')');
|
||||
@@ -94,6 +96,11 @@ class FormulaPropertyPath {
|
||||
DeployPropertyParser parser = descriptor.parser().setCatchFirst(true);
|
||||
|
||||
String parsed = parser.parse(internalExpression);
|
||||
if (path != null) {
|
||||
// fetch("machineStats", "sum(hours), sum(totalKms)")
|
||||
parsed = parsed.replace("${}", "${" + path + "}");
|
||||
}
|
||||
|
||||
ElPropertyDeploy firstProp = parser.getFirstProp();
|
||||
|
||||
ScalarType<?> scalarType;
|
||||
|
||||
@@ -51,6 +51,15 @@ public final class TableJoin {
|
||||
this.queryHash = calcQueryHash();
|
||||
}
|
||||
|
||||
private TableJoin(TableJoin source, String overrideColumn) {
|
||||
this.table = source.table;
|
||||
this.type = source.type;
|
||||
this.inheritInfo = source.inheritInfo;
|
||||
this.columns = new TableJoinColumn[1];
|
||||
this.columns[0] = source.columns[0].withOverrideColumn(overrideColumn);
|
||||
this.queryHash = calcQueryHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a hash value for adding to a query plan.
|
||||
*/
|
||||
@@ -166,4 +175,11 @@ public final class TableJoin {
|
||||
sb.append(a2).append(".").append(pair.getForeignDbColumn());
|
||||
}
|
||||
}
|
||||
|
||||
TableJoin withOverrideColumn(String overrideColumn) {
|
||||
if (columns.length == 1 && overrideColumn != null && !overrideColumn.equals(columns[0].getLocalDbColumn())) {
|
||||
return new TableJoin(this, overrideColumn);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,16 @@ public class TableJoinColumn {
|
||||
this.queryHash = hash();
|
||||
}
|
||||
|
||||
private TableJoinColumn(TableJoinColumn source, String overrideColumn) {
|
||||
this.localDbColumn = InternString.intern(overrideColumn);
|
||||
this.foreignDbColumn = source.foreignDbColumn;
|
||||
this.localSqlFormula = null;
|
||||
this.foreignSqlFormula = null;
|
||||
this.insertable = source.isInsertable();
|
||||
this.updateable = source.isUpdateable();
|
||||
this.queryHash = hash();
|
||||
}
|
||||
|
||||
int hash() {
|
||||
int result = localDbColumn != null ? localDbColumn.hashCode() : 0;
|
||||
result = 92821 * result + (foreignDbColumn != null ? foreignDbColumn.hashCode() : 0);
|
||||
@@ -123,4 +133,7 @@ public class TableJoinColumn {
|
||||
return foreignSqlFormula;
|
||||
}
|
||||
|
||||
TableJoinColumn withOverrideColumn(String overrideColumn) {
|
||||
return new TableJoinColumn(this, overrideColumn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,4 +207,10 @@ public interface IdBinder {
|
||||
* Cast or convert the Id value if necessary.
|
||||
*/
|
||||
Object convertId(Object idValue);
|
||||
|
||||
/**
|
||||
* Return a key to use for bean caches given the id value.
|
||||
*/
|
||||
String cacheKey(Object idValue);
|
||||
|
||||
}
|
||||
|
||||
@@ -479,4 +479,20 @@ public final class IdBinderEmbedded implements IdBinder {
|
||||
|
||||
return idValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object value) {
|
||||
|
||||
EntityBean bean = (EntityBean)value;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (BeanProperty prop : props) {
|
||||
Object val = prop.getValue(bean);
|
||||
if (val != null) {
|
||||
sb.append(prop.format(val));
|
||||
}
|
||||
sb.append("|");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -178,4 +178,10 @@ public final class IdBinderEmpty implements IdBinder {
|
||||
public void writeData(DataOutput dataOutput, Object idValue) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -262,4 +262,10 @@ public final class IdBinderSimple implements IdBinder {
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object value) {
|
||||
return scalarType.format(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import javax.persistence.ElementCollection;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.MapKey;
|
||||
import javax.persistence.MapKeyColumn;
|
||||
@@ -271,6 +272,10 @@ class AnnotationAssocManys extends AnnotationParser {
|
||||
valueProp.setDbLength(column.length());
|
||||
valueProp.setDbScale(column.scale());
|
||||
}
|
||||
Lob lob = get(prop, Lob.class);
|
||||
if (lob != null) {
|
||||
util.setLobType(valueProp);
|
||||
}
|
||||
elementDescriptor.addBeanProperty(valueProp);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import io.ebean.annotation.Index;
|
||||
import io.ebean.annotation.JsonIgnore;
|
||||
import io.ebean.annotation.Length;
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
import io.ebean.annotation.Sum;
|
||||
import io.ebean.annotation.TenantId;
|
||||
import io.ebean.annotation.UnmappedJson;
|
||||
import io.ebean.annotation.UpdatedTimestamp;
|
||||
@@ -132,6 +133,7 @@ public class AnnotationFields extends AnnotationParser {
|
||||
prop.setId();
|
||||
prop.setNullable(false);
|
||||
prop.setEmbedded();
|
||||
info.setEmbeddedId(prop);
|
||||
}
|
||||
|
||||
DocEmbedded docEmbedded = get(prop, DocEmbedded.class);
|
||||
@@ -284,6 +286,10 @@ public class AnnotationFields extends AnnotationParser {
|
||||
if (aggregation != null) {
|
||||
prop.setAggregation(aggregation.value());
|
||||
}
|
||||
Sum sum = get(prop, Sum.class);
|
||||
if (sum != null) {
|
||||
prop.setAggregation("sum(" + prop.getName() + ")");
|
||||
}
|
||||
|
||||
Version version = get(prop, Version.class);
|
||||
if (version != null) {
|
||||
|
||||
@@ -54,6 +54,7 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
} else {
|
||||
prop.setId();
|
||||
prop.setEmbedded();
|
||||
info.setEmbeddedId(prop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
} else {
|
||||
prop.setId();
|
||||
if (prop.getPropertyType().equals(UUID.class)) {
|
||||
if (descriptor.getIdGeneratorName() == null) {
|
||||
if (readConfig.isIdGeneratorAutomatic() && descriptor.getIdGeneratorName() == null) {
|
||||
descriptor.setUuidGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy.parse;
|
||||
import io.ebean.RawSql;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
@@ -23,6 +24,8 @@ public class DeployBeanInfo<T> {
|
||||
|
||||
private final DeployBeanDescriptor<T> descriptor;
|
||||
|
||||
private DeployBeanPropertyAssoc<?> embeddedId;
|
||||
|
||||
/**
|
||||
* Create with a DeployUtil and BeanDescriptor.
|
||||
*/
|
||||
@@ -89,4 +92,19 @@ public class DeployBeanInfo<T> {
|
||||
public void setPrimaryKeyJoin(TableJoin join) {
|
||||
descriptor.setPrimaryKeyJoin(join);
|
||||
}
|
||||
|
||||
/**
|
||||
* This bean type has an embedded Id property.
|
||||
*/
|
||||
public void setEmbeddedId(DeployBeanPropertyAssoc<?> embeddedId) {
|
||||
this.embeddedId = embeddedId;
|
||||
}
|
||||
|
||||
public Class<?> getEmbeddedIdType() {
|
||||
return (embeddedId == null) ? null : embeddedId.getTargetType();
|
||||
}
|
||||
|
||||
public boolean isEmbedded() {
|
||||
return descriptor.isEmbedded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class ReadAnnotationConfig {
|
||||
private final boolean eagerFetchLobs;
|
||||
private final boolean javaxValidationAnnotations;
|
||||
private final boolean jacksonAnnotations;
|
||||
private final boolean idGeneratorAutomatic;
|
||||
|
||||
ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, ServerConfig serverConfig) {
|
||||
|
||||
@@ -23,6 +24,7 @@ class ReadAnnotationConfig {
|
||||
this.versionsBetweenSuffix = versionsBetweenSuffix;
|
||||
this.disableL2Cache = serverConfig.isDisableL2Cache();
|
||||
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
|
||||
this.idGeneratorAutomatic = serverConfig.isIdGeneratorAutomatic();
|
||||
|
||||
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
|
||||
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
|
||||
@@ -48,6 +50,10 @@ class ReadAnnotationConfig {
|
||||
return eagerFetchLobs;
|
||||
}
|
||||
|
||||
public boolean isIdGeneratorAutomatic() {
|
||||
return idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
boolean isJavaxValidationAnnotations() {
|
||||
return javaxValidationAnnotations;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
|
||||
/**
|
||||
* Request to map a resultSet columns for a query into a DTO bean.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
abstract class DtoQueryPlanBase implements DtoQueryPlan {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user