mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
293
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b3e485beb | ||
|
|
d4be0718a8 | ||
|
|
6d0556a120 | ||
|
|
ec7e4e4935 | ||
|
|
471605619a | ||
|
|
02fe956056 | ||
|
|
db13034fec | ||
|
|
c2489beb82 | ||
|
|
9a424d7d6d | ||
|
|
9b74602617 | ||
|
|
5c99986476 | ||
|
|
7d38933e9d | ||
|
|
7668fb11a9 | ||
|
|
eac68166d9 | ||
|
|
cb0023345f | ||
|
|
f0f7feb125 | ||
|
|
c8e32253a5 | ||
|
|
01541bb208 | ||
|
|
2d44d06f36 | ||
|
|
fda930a162 | ||
|
|
5a6cdb9a86 | ||
|
|
5ef7fe8504 | ||
|
|
11586fb635 | ||
|
|
d555122067 | ||
|
|
b6d3dc3516 | ||
|
|
bbf54d6196 | ||
|
|
74c88ce511 | ||
|
|
309bf6a11a | ||
|
|
02b6ab0f74 | ||
|
|
0c41993ce7 | ||
|
|
d949c48245 | ||
|
|
93e603ca25 | ||
|
|
117d22fd67 | ||
|
|
f13de2c8a6 | ||
|
|
5b8ef8154a | ||
|
|
df892f4509 | ||
|
|
2755e6d1ec | ||
|
|
2024193da9 | ||
|
|
72788c7428 | ||
|
|
8789709fb8 | ||
|
|
588aaf9361 | ||
|
|
68649d0e98 | ||
|
|
f6047d3b73 | ||
|
|
629e096e28 | ||
|
|
c981944c8e | ||
|
|
1ba9f4e9de | ||
|
|
ce02e7ce70 | ||
|
|
1d9a2ed43a | ||
|
|
ae81ce5f47 | ||
|
|
9e42f98902 | ||
|
|
ada21729ec | ||
|
|
8b7d882b8a | ||
|
|
cf2d9df256 | ||
|
|
48b05eb358 | ||
|
|
0796fc0d88 | ||
|
|
df585644e9 | ||
|
|
ff3c5b3d82 | ||
|
|
1a03e84f0e | ||
|
|
49ab17cd34 | ||
|
|
9cd189034c | ||
|
|
ed566ca153 | ||
|
|
9159cec36c | ||
|
|
b58fc2df04 | ||
|
|
dd8607b072 | ||
|
|
a5a8765bd0 | ||
|
|
ae55b58438 | ||
|
|
671d082ad3 | ||
|
|
43e95d81f6 | ||
|
|
3a64cf6952 | ||
|
|
8d976f83a9 | ||
|
|
117737c790 | ||
|
|
085843339f | ||
|
|
bf33b3f96f | ||
|
|
e1d4159453 | ||
|
|
4f1f3cadf9 | ||
|
|
01e1a4642b | ||
|
|
3a75e1f66c | ||
|
|
47ac4c5c5b | ||
|
|
9c73309dfd | ||
|
|
bd3c99e67f | ||
|
|
5adbebaf6e | ||
|
|
6a9ab49ef0 | ||
|
|
4308a98a0a | ||
|
|
f86f1b2647 | ||
|
|
96cc8d7605 | ||
|
|
59537227b6 | ||
|
|
81c6d4601d | ||
|
|
e21839de91 | ||
|
|
a61db2ab35 | ||
|
|
bddfa3bc5f | ||
|
|
9ad460ecd8 | ||
|
|
50593a7f9d | ||
|
|
c088cfa339 | ||
|
|
e683e25a95 | ||
|
|
c7052c5bfa | ||
|
|
698ee2198d | ||
|
|
91c4eca528 | ||
|
|
81897bd54d | ||
|
|
2427630c07 | ||
|
|
3238c0def4 | ||
|
|
93f75151f0 | ||
|
|
32e9b48359 | ||
|
|
258e29d2f0 | ||
|
|
ec2f1f7243 | ||
|
|
5775985468 | ||
|
|
4788dd4ec9 | ||
|
|
ca7e5b79b8 | ||
|
|
90dfbcd7b3 | ||
|
|
6b39bad4da | ||
|
|
25dea0b5b0 | ||
|
|
0069d2fabc | ||
|
|
98964638ba | ||
|
|
4573a995f1 | ||
|
|
4bc80cc724 | ||
|
|
a2972abe3e | ||
|
|
54eaf561f4 | ||
|
|
3134dd3165 | ||
|
|
8fc6abd480 | ||
|
|
f031bb72b6 | ||
|
|
42f0b419e8 | ||
|
|
7cbae758fc | ||
|
|
fb32cfa170 | ||
|
|
ffa8268125 | ||
|
|
eba1003c19 | ||
|
|
eca6383dba | ||
|
|
624d08f46c | ||
|
|
14749daf9b | ||
|
|
2817875706 | ||
|
|
3f8d705439 | ||
|
|
e9faef2692 | ||
|
|
d459807af7 | ||
|
|
10ad9ef096 | ||
|
|
11e3649b85 | ||
|
|
ddc00fedb6 | ||
|
|
0aa9208dcc | ||
|
|
e3b1996dc9 | ||
|
|
24ca744e06 | ||
|
|
7f9ce5bdd4 | ||
|
|
23e5de0819 | ||
|
|
c23508819f | ||
|
|
af030e3da4 | ||
|
|
d8e67d09a9 | ||
|
|
66c08e663e | ||
|
|
9a096e272e | ||
|
|
21e87e12cb | ||
|
|
7c6d7dd4d0 | ||
|
|
c5329e9db0 | ||
|
|
bccb9f8ddb | ||
|
|
c57af29fca | ||
|
|
37b6a321ab | ||
|
|
896f7d9ea8 | ||
|
|
141a0d9358 | ||
|
|
5a7f24da5d | ||
|
|
8ea86d6c6d | ||
|
|
fb5eeb60b1 | ||
|
|
33ec8abb9d | ||
|
|
bbd7baf1be | ||
|
|
2e36402a76 | ||
|
|
fa4af4ef98 | ||
|
|
ed6a9e2dbb | ||
|
|
42d83705b7 | ||
|
|
ae58d9223a | ||
|
|
a9a89938f9 | ||
|
|
4b91f5fe0b | ||
|
|
85d3caf079 | ||
|
|
f6d8c8b618 | ||
|
|
d0fa3f0cb1 | ||
|
|
41a31c1aa0 | ||
|
|
ab301f970c | ||
|
|
d1b7b082a2 | ||
|
|
5fb3a1e019 | ||
|
|
cb0dee998b | ||
|
|
53b606289d | ||
|
|
574d5e215b | ||
|
|
508f2129f5 | ||
|
|
cdfcbeec43 | ||
|
|
0e9782e6b3 | ||
|
|
927fa5f7d3 | ||
|
|
53a80c7dd1 | ||
|
|
89789f3347 | ||
|
|
9dc5ca602c | ||
|
|
95737cd3c5 | ||
|
|
0a49fc9b62 | ||
|
|
f8c80c7fd2 | ||
|
|
fc837b6472 | ||
|
|
cfaedcdbc5 | ||
|
|
111bac7514 | ||
|
|
1a2ca63d65 | ||
|
|
33031d1c69 | ||
|
|
49d9d88fbe | ||
|
|
756f867ed2 | ||
|
|
12108ada38 | ||
|
|
fb2fdc9b53 | ||
|
|
d570971fd1 | ||
|
|
a8434f22ff | ||
|
|
b0d3921264 | ||
|
|
4167dfc4af | ||
|
|
e49a0c9ffb | ||
|
|
dd6f223ae1 | ||
|
|
05aff53241 | ||
|
|
9a3362e842 | ||
|
|
04e0069eb3 | ||
|
|
b8c4e1c3d3 | ||
|
|
6b136248fa | ||
|
|
aa004f11af | ||
|
|
acd5578d8a | ||
|
|
07946cd505 | ||
|
|
57870aaa05 | ||
|
|
cca837bcfa | ||
|
|
534eb2f0e2 | ||
|
|
dfa7b765f9 | ||
|
|
4ef581f87c | ||
|
|
eb96c726fd | ||
|
|
abbcd35ee6 | ||
|
|
db5a2b194d | ||
|
|
74e0a16531 | ||
|
|
8601f51a6c | ||
|
|
6534feb5da | ||
|
|
6cf5e3386d | ||
|
|
7651256083 | ||
|
|
057d7480b0 | ||
|
|
85c4385498 | ||
|
|
74a9019535 | ||
|
|
c44931ea25 | ||
|
|
012322503f | ||
|
|
5575273c96 | ||
|
|
f3251691e2 | ||
|
|
9d336cb20b | ||
|
|
9895e48485 | ||
|
|
4799c56e25 | ||
|
|
602a35d0b6 | ||
|
|
4bc1f2ede4 | ||
|
|
674617eb9e | ||
|
|
afead2da41 | ||
|
|
607cf3156c | ||
|
|
af2b8976fa | ||
|
|
85bfe3d03e | ||
|
|
eda6350d89 | ||
|
|
8ac1b745df | ||
|
|
6c8cf0c5b7 | ||
|
|
d0b24dc340 | ||
|
|
2042bdf29a | ||
|
|
4f327c2751 | ||
|
|
fbc2ba3912 | ||
|
|
93d659050b | ||
|
|
2520837f5e | ||
|
|
2f349ff054 | ||
|
|
0aec597cf2 | ||
|
|
5faec61cd2 | ||
|
|
7252e2d7d9 | ||
|
|
0cc87009d0 | ||
|
|
ef58954af5 | ||
|
|
824f5e468e | ||
|
|
d84cdab714 | ||
|
|
5a3550e603 | ||
|
|
f840cfc2ae | ||
|
|
8e0d2f8749 | ||
|
|
eef514f922 | ||
|
|
4fc7898f85 | ||
|
|
6258a5d263 | ||
|
|
dbf5f3b57b | ||
|
|
4b594fb533 | ||
|
|
c0efd49c73 | ||
|
|
54ba1480eb | ||
|
|
4d8bc0a2ce | ||
|
|
e182190f96 | ||
|
|
8711ebc6f7 | ||
|
|
baa7242939 | ||
|
|
47e8d27212 | ||
|
|
70aed3906e | ||
|
|
70c3860a65 | ||
|
|
1ba21d5c43 | ||
|
|
2019bc7bf1 | ||
|
|
3cb52917e3 | ||
|
|
55a257a64c | ||
|
|
83ef204f3c | ||
|
|
a18cac035b | ||
|
|
873c14faaf | ||
|
|
e1af7c29eb | ||
|
|
c1c85b67c2 | ||
|
|
05b8adbf6e | ||
|
|
3ca4efc818 | ||
|
|
ec2c3b13d4 | ||
|
|
5a3e386b7c | ||
|
|
7e56a147be | ||
|
|
62cdfb3209 | ||
|
|
740532da4b | ||
|
|
130356cb3b | ||
|
|
20e46cad9b | ||
|
|
9953ef35eb | ||
|
|
fdafa776e1 | ||
|
|
392eb2744d | ||
|
|
a4415b03ff |
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
|
||||
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
|
||||
|
||||
## Current versions
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
|
||||
* [](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
|
||||
|
||||
|
||||
@@ -9,15 +9,20 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.13.1</version>
|
||||
<version>11.19.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
<url>http://ebean-orm.github.io/</url>
|
||||
|
||||
<properties>
|
||||
<jackson-core.version>2.9.5</jackson-core.version>
|
||||
<snakeyaml.version>1.21</snakeyaml.version>
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.13.1</tag>
|
||||
<tag>ebean-11.19.2</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -89,7 +94,7 @@
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.19</version>
|
||||
<version>${snakeyaml.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
@@ -112,13 +117,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.8</version>
|
||||
<version>4.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -130,27 +135,27 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.3.1</version>
|
||||
<version>11.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>antlr4-runtime</artifactId>
|
||||
<version>4.6</version>
|
||||
<version>4.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson core used internally by Ebean -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jackson-core.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -187,7 +192,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.4.1212</version>
|
||||
<version>42.2.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -224,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.10.1</version>
|
||||
<version>11.11.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -254,7 +259,7 @@
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>6.1.3.jre8-preview</version>
|
||||
<version>6.4.0.jre8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -297,7 +302,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.10.1</version>
|
||||
<version>11.11.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
@@ -355,7 +360,7 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 10</doctitle>
|
||||
<doctitle>Ebean 11</doctitle>
|
||||
<overview>src/main/java/io/ebean/overview.html</overview>
|
||||
<source>1.8</source>
|
||||
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.ebean;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
|
||||
* <p>
|
||||
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
db().save(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans in the collection.
|
||||
*/
|
||||
public int saveAll(Collection<T> bean) {
|
||||
return db().saveAll(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this entity.
|
||||
*
|
||||
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().delete(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the beans in the collection.
|
||||
*/
|
||||
public int deleteAll(Collection<T> beans) {
|
||||
return db().deleteAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bean permanently without soft delete.
|
||||
* <p>
|
||||
@@ -167,6 +183,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
|
||||
return db().deletePermanent(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the default merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object)
|
||||
*/
|
||||
public void merge(T bean) {
|
||||
db().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge this entity using the specified merge options.
|
||||
* <p>
|
||||
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
|
||||
* insert or an update based on that.
|
||||
*
|
||||
* @see EbeanServer#merge(Object, MergeOptions)
|
||||
*/
|
||||
public void merge(T bean, MergeOptions options) {
|
||||
db().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes this entity from the database.
|
||||
*
|
||||
|
||||
@@ -638,6 +638,25 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
public static void merge(Object bean) {
|
||||
serverMgr.getDefaultServer().merge(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
public static void merge(Object bean, MergeOptions options) {
|
||||
serverMgr.getDefaultServer().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans from a Collection.
|
||||
*/
|
||||
|
||||
@@ -437,6 +437,19 @@ public interface EbeanServer {
|
||||
*/
|
||||
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
|
||||
|
||||
/**
|
||||
* Create a named Query for DTO beans.
|
||||
* <p>
|
||||
* DTO beans are just normal bean like classes with public constructor(s) and setters.
|
||||
* They do not need to be registered with Ebean before use.
|
||||
* </p>
|
||||
*
|
||||
* @param dtoType The type of the DTO bean the rows will be mapped into.
|
||||
* @param namedQuery The name of the query
|
||||
* @param <T> The type of the DTO bean.
|
||||
*/
|
||||
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
|
||||
|
||||
/**
|
||||
* Create a SqlQuery for executing native sql
|
||||
* query statements.
|
||||
@@ -508,30 +521,44 @@ public interface EbeanServer {
|
||||
* etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>Using try with resources</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* ebeanServer.beginTransaction();
|
||||
*
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
*
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
* ...
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Using try finally block</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a transaction (stored in a ThreadLocal)
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* Order order = ebeanServer.find(Order.class,10);
|
||||
*
|
||||
* ebeanServer.save(order);
|
||||
*
|
||||
* ebeanServer.commitTransaction();
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if we didn't commit
|
||||
* // i.e. an exception occurred before commitTransaction().
|
||||
* ebeanServer.endTransaction();
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Transaction options:</h3>
|
||||
* <h3>Transaction options</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Transaction txn = ebeanServer.beginTransaction();
|
||||
* try {
|
||||
* try (Transaction txn = ebeanServer.beginTransaction()) {
|
||||
* // explicitly turn on/off JDBC batch use
|
||||
* txn.setBatchMode(true);
|
||||
* txn.setBatchSize(50);
|
||||
@@ -552,10 +579,6 @@ public interface EbeanServer {
|
||||
* ...
|
||||
*
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // rollback if necessary
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -585,19 +608,16 @@ public interface EbeanServer {
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
* // suspend it until this transaction ends
|
||||
* Transaction txn = server.beginTransaction(TxScope.requiresNew());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.requiresNew())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which:
|
||||
* // A) will rollback transaction if it has not been committed already
|
||||
* // At end this transaction will:
|
||||
* // A) will rollback transaction if it has not been committed
|
||||
* // B) will restore a previously suspended transaction
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -606,20 +626,13 @@ public interface EbeanServer {
|
||||
* <pre>{@code
|
||||
*
|
||||
* // start a new transaction if there is not a current transaction
|
||||
* Transaction txn = server.beginTransaction(TxScope.required());
|
||||
* try {
|
||||
* try (Transaction txn = server.beginTransaction(TxScope.required())) {
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* // commit the transaction if it was created or
|
||||
* // do nothing if there was already a current transaction
|
||||
* txn.commit();
|
||||
*
|
||||
* } finally {
|
||||
* // end this transaction which will rollback the transaction
|
||||
* // if it was created for this try finally scope and has not
|
||||
* // already been committed
|
||||
* txn.end();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -795,411 +808,18 @@ public interface EbeanServer {
|
||||
<T> T getReference(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* Return the extended API for EbeanServer.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* The extended API has the options for executing queries that take an explicit
|
||||
* transaction as an argument.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* Typically we only need to use the extended API when we do NOT want to use the
|
||||
* usual ThreadLocal based mechanism to obtain the current transaction but instead
|
||||
* supply the transaction explicitly.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link #update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
ExtendedServer extended();
|
||||
|
||||
/**
|
||||
* Either Insert or Update the bean depending on its state.
|
||||
@@ -1615,6 +1235,29 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the default merge options (no paths specified, default delete).
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
*/
|
||||
void merge(Object bean);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options and a transaction.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Insert the bean.
|
||||
* <p>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
/**
|
||||
* Class to determine the ebean version. (
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
public class EbeanVersion {
|
||||
private EbeanVersion() {
|
||||
|
||||
|
||||
}
|
||||
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
|
||||
|
||||
|
||||
private static String version = "unknown";
|
||||
static {
|
||||
try {
|
||||
Properties prop = new Properties();
|
||||
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
|
||||
if (in != null) {
|
||||
prop.load(in);
|
||||
in.close();
|
||||
version = prop.getProperty("version");
|
||||
}
|
||||
}
|
||||
logger.info("ebean version: {}", version);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Could not determine ebean version: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
|
||||
*/
|
||||
public static String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -327,6 +327,30 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression allEq(Map<String, Object> propertyMap);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
Expression bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
@@ -404,6 +412,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> select(String properties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Set whether this query uses DISTINCT.
|
||||
* <p>
|
||||
@@ -482,7 +495,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<CountedValue<Order.Status>> orderStatusCount =
|
||||
@@ -545,6 +558,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Query<T> setDisableReadAuditing();
|
||||
|
||||
/**
|
||||
* Set a label on the query (to help identify query execution statistics).
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Add expressions to the having clause.
|
||||
* <p>
|
||||
@@ -873,7 +891,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -883,7 +901,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -893,7 +911,7 @@ public interface ExpressionList<T> {
|
||||
|
||||
/**
|
||||
* In - property has a value in the collection of values.
|
||||
*
|
||||
* <p>
|
||||
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
|
||||
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
|
||||
*/
|
||||
@@ -991,6 +1009,67 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> arrayIsNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Add expression for ANY of the given bit flags to be set.
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAny("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAny(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for ALL of the given bit flags to be set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAll(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add expression for the given bit flags to be NOT set.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseNot(String propertyName, long flags);
|
||||
|
||||
/**
|
||||
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Flags Bulk + Size = Size
|
||||
* // ... meaning Bulk is not set and Size is set
|
||||
*
|
||||
* long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
|
||||
* long mask = BwFlags.HAS_SIZE; // Only Size flag set
|
||||
*
|
||||
* where().bitwiseAnd("flags", selectedFlags, mask)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName The property that holds the flags value
|
||||
* @param flags The flags we are looking for
|
||||
*/
|
||||
ExpressionList<T> bitwiseAnd(String propertyName, long flags, long match);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.time.Clock;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* The extended API for EbeanServer.
|
||||
* <p>
|
||||
* This provides the finder methods that take an explicit transaction rather than obtaining
|
||||
* the transaction from the usual mechanism (which is ThreadLocal based).
|
||||
* </p>
|
||||
* <p>
|
||||
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
|
||||
* the use of the mechanism to get the current transaction and instead explicitly supply
|
||||
* the transaction to use.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
|
||||
* will use the normal mechanism to obtain the transaction to use.
|
||||
* </p>
|
||||
*/
|
||||
public interface ExtendedServer {
|
||||
|
||||
/**
|
||||
* Return the NOW time from the Clock.
|
||||
*/
|
||||
long clockNow();
|
||||
|
||||
/**
|
||||
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
|
||||
* <p>
|
||||
* Note that we only expect to change the Clock for testing purposes.
|
||||
* </p>
|
||||
*/
|
||||
void setClock(Clock clock);
|
||||
|
||||
/**
|
||||
* Return the number of 'top level' or 'root' entities this query should return.
|
||||
*
|
||||
* @see Query#findCount()
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
<T> int findCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return the Id values of the query as a List.
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a QueryIterator for the query.
|
||||
* <p>
|
||||
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
|
||||
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
|
||||
* to findIterate(). The reason is that those methods automatically take care of
|
||||
* closing the queryIterator (and the underlying jdbc statement and resultSet).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is similar to findEach in that not all the result beans need to be held
|
||||
* in memory at the same time and as such is good for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#findIterate()
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEach((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query visiting the each bean one at a time.
|
||||
* <p>
|
||||
* Compared to findEach() this provides the ability to stop processing the query
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
* a very large resultSet. The reason is that not all the result beans need to be
|
||||
* held in memory at the same time and instead processed one at a time.
|
||||
* </p>
|
||||
* <p>
|
||||
* Internally this query using a PersistenceContext scoped to each bean (and the
|
||||
* beans associated object graph).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.find(Order.class)
|
||||
* .where().eq("status", Order.Status.NEW)
|
||||
* .order().asc("id")
|
||||
* .findEachWhile((Order order) -> {
|
||||
*
|
||||
* // do something with the order bean
|
||||
* System.out.println(" -- processing order ... " + order);
|
||||
*
|
||||
* boolean carryOnProcessing = ...
|
||||
* return carryOnProcessing;
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
* Generally this query is expected to be a find by id or unique predicates query.
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a query returning a list of beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the row count on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find Id's query in a background thread.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a
|
||||
* timeout).
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute the fetch Id's on
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute find list query in a background thread returning a FutureList object.
|
||||
* <p>
|
||||
* This returns a Future object which can be used to cancel, check the
|
||||
* execution status (isDone etc) and get the value (with or without a timeout).
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @param query the query to execute in the background
|
||||
* @param transaction the transaction (can be null).
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList = Ebean.find(Order.class)
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a set of entity beans.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findSet()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Set<Customer> customers =
|
||||
* ebeanServer.find(Customer.class)
|
||||
* .where().ilike("name", "rob%")
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning the entity beans in a Map.
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findMap()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of values for a single property.
|
||||
* <p>
|
||||
* <h3>Example 1:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .orderBy().asc("name")
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Example 2:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<String> names =
|
||||
* Ebean.find(Customer.class)
|
||||
* .setDistinct(true)
|
||||
* .select("name")
|
||||
* .where().eq("status", Customer.Status.NEW)
|
||||
* .orderBy().asc("name")
|
||||
* .setMaxRows(100)
|
||||
* .findSingleAttributeList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query returning at most one entity bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* This will throw a NonUniqueResultException if the query finds more than one result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link Query#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched beans.
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findOne(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
* <p>
|
||||
* Note that if the query includes joins then the generated delete statement may not be
|
||||
* optimal depending on the database platform.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query used for the delete
|
||||
* @param transaction the transaction to use (can be null)
|
||||
* @param <T> the type of entity bean to fetch.
|
||||
* @return the number of beans/rows that were deleted
|
||||
*/
|
||||
<T> int delete(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the update query returning the number of rows updated.
|
||||
* <p>
|
||||
* The update query must be created using {@link EbeanServer#update(Class)}.
|
||||
* </p>
|
||||
*
|
||||
* @param query the update query to execute
|
||||
* @param transaction the optional transaction to use for the update (can be null)
|
||||
* @param <T> the type of entity bean
|
||||
* @return The number of rows updated
|
||||
*/
|
||||
<T> int update(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a list of MapBean.
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findList()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql query returning a single MapBean or null.
|
||||
* <p>
|
||||
* This will throw a PersistenceException if the query found more than one
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findOne()} rather than
|
||||
* explicitly calling this method. You could use this method if you wish to
|
||||
* explicitly control the transaction used for the query.
|
||||
* </p>
|
||||
*
|
||||
* @param query the query to execute.
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @return the fetched MapBean or null if none was found.
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Defines what part of the object graph to load (select and fetch clauses).
|
||||
* <p>
|
||||
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
|
||||
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
|
||||
* that can be defined separately from the query and combined with other FetchGroups.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Select example</h3>*
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Select and fetch example</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Combining FetchGroups</h3>
|
||||
* <p>
|
||||
* FetchGroups can be combined together to form another FetchGroup.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
|
||||
* .select("line1, line2, city")
|
||||
* .fetch("country", "name")
|
||||
* .build();
|
||||
*
|
||||
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
|
||||
* .select("name, version")
|
||||
* .fetch("billingAddress", FG_ADDRESS)
|
||||
* .build();
|
||||
*
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(FG_CUSTOMER)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The bean type the Fetch group can be applied to
|
||||
*/
|
||||
public interface FetchGroup<T> {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
* <p>
|
||||
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param select The select clause of the FetchGroup
|
||||
*
|
||||
* @return The FetchGroup with the given select clause
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroup<T> of(Class<T> cls, String select) {
|
||||
return XServiceProvider.fetchGroupOf(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
|
||||
* <p>
|
||||
* We chain select() with one or more fetch() clauses to define the object graph to load.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
|
||||
*/
|
||||
@Nonnull
|
||||
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
|
||||
return XServiceProvider.fetchGroupOf(cls);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Builds a FetchGroup by adding fetch clauses.
|
||||
* <p>
|
||||
* We add select() and fetch() clauses to define the object graph we want to load.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* FetchGroup fetchGroup = FetchGroup
|
||||
* .select("name, status")
|
||||
* .fetch("contacts", "firstName, lastName, email")
|
||||
* .build();
|
||||
*
|
||||
* Customer.query()
|
||||
* .select(fetchGroup)
|
||||
* .where()
|
||||
* ...
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FetchGroupBuilder<T> {
|
||||
|
||||
/**
|
||||
* Specify specific properties to select (top level properties).
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> select(String select);
|
||||
|
||||
/**
|
||||
* Fetch all the properties at the given path.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path using a query join with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path lazily with the nested fetch group.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetch(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties using a query join.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchQuery(String path, String properties);
|
||||
|
||||
/**
|
||||
* Fetch the path including all its properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path);
|
||||
|
||||
/**
|
||||
* Fetch the path including specified properties lazily.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroupBuilder<T> fetchLazy(String path, String properties);
|
||||
|
||||
/**
|
||||
* Build and return the FetchGroup.
|
||||
*/
|
||||
@Nonnull
|
||||
FetchGroup<T> build();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
|
||||
* <p>
|
||||
* Instances of MergeOptions are thread safe and safe to share across threads.
|
||||
*/
|
||||
public interface MergeOptions {
|
||||
|
||||
/**
|
||||
* Returns true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
boolean isClientGeneratedIds();
|
||||
|
||||
/**
|
||||
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
|
||||
*/
|
||||
boolean isDeletePermanent();
|
||||
|
||||
/**
|
||||
* Return the paths included in the merge.
|
||||
*/
|
||||
Set<String> paths();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Builds a MergeOptions which is immutable and thread safe.
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private static final MOptions DEFAULT_OPTIONS = new MOptions();
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent;
|
||||
|
||||
/**
|
||||
* Return the default options.
|
||||
*/
|
||||
public static MergeOptions defaultOptions() {
|
||||
return DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
*
|
||||
* @param path The path relative to the root type.
|
||||
* @return The builder to chain another addPath() or build().
|
||||
*/
|
||||
public MergeOptionsBuilder addPath(String path) {
|
||||
paths.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
public MergeOptionsBuilder setClientGeneratedIds() {
|
||||
this.clientGeneratedIds = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
|
||||
*/
|
||||
public MergeOptionsBuilder setDeletePermanent() {
|
||||
this.deletePermanent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MergeOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
private static class MOptions implements MergeOptions {
|
||||
|
||||
private final boolean clientGeneratedIds;
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(){
|
||||
this.clientGeneratedIds = false;
|
||||
this.paths = new LinkedHashSet<>();
|
||||
this.deletePermanent = false;
|
||||
}
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientGeneratedIds() {
|
||||
return clientGeneratedIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeletePermanent() {
|
||||
return deletePermanent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,6 @@ class PrimaryServer {
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = "db";
|
||||
}
|
||||
System.setProperty("ebean_db", defaultServerName);
|
||||
return defaultServerName;
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,14 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Convert the query to a DTO bean query.
|
||||
* <p>
|
||||
* We effectively use the underlying ORM query to build the SQL and then execute
|
||||
* and map it into DTO beans.
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -389,6 +397,11 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> select(String fetchProperties);
|
||||
|
||||
/**
|
||||
* Apply the fetchGroup which defines what part of the object graph to load.
|
||||
*/
|
||||
Query<T> select(FetchGroup<T> fetchGroup);
|
||||
|
||||
/**
|
||||
* Specify a path to fetch eagerly including specific properties.
|
||||
* <p>
|
||||
@@ -594,8 +607,6 @@ public interface Query<T> {
|
||||
* <p>
|
||||
* This query will execute against the EbeanServer that was used to create it.
|
||||
* </p>
|
||||
*
|
||||
* @see EbeanServer#findIds(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
@@ -732,8 +743,6 @@ public interface Query<T> {
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findList(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
@@ -751,8 +760,6 @@ public interface Query<T> {
|
||||
* .findSet();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findSet(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
@@ -774,8 +781,6 @@ public interface Query<T> {
|
||||
* .findMap();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see EbeanServer#findMap(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
@@ -1411,6 +1416,15 @@ public interface Query<T> {
|
||||
*/
|
||||
Query<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set a label on the query.
|
||||
* <p>
|
||||
* This label can be used to help identify query performance metrics but we can also use
|
||||
* profile location enhancement on Finders so for some that would be a better option.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowConsumer {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and process it.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
void accept(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Used with SqlQuery to map raw JDBC ResultSet to objects.
|
||||
* <p>
|
||||
* This provides a low level mapping option with direct use of JDBC ResultSet
|
||||
* with the option of having logic in the mapping. For example, only map some
|
||||
* columns depending on the values read from other columns.
|
||||
* </p>
|
||||
* <p>
|
||||
* For straight mapping into beans then DtoQuery would be the first choice as
|
||||
* it can automatically map the ResultSet into beans.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* //
|
||||
* // A mapper from ResultSet into our CustomerDto bean
|
||||
* //
|
||||
* class CustomerMapper implements RowMapper<CustomerDto> {
|
||||
*
|
||||
* @Override
|
||||
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
|
||||
*
|
||||
* long id = rset.getLong(1);
|
||||
* String name = rset.getString(2);
|
||||
* String status = rset.getString(3);
|
||||
*
|
||||
* return new CustomerDto(id, name, status);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
* //
|
||||
* // Then use the mapper
|
||||
* //
|
||||
*
|
||||
* String sql = "select id, name, status from o_customer where name = ?";
|
||||
*
|
||||
* CustomerDto rob = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, "Rob")
|
||||
* .findOne(CUSTOMER_MAPPER);
|
||||
*
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> The type the row data is mapped into.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RowMapper<T> {
|
||||
|
||||
/**
|
||||
* Read the data from the ResultSet and map to the return type.
|
||||
*
|
||||
* @param resultSet The JDBC ResultSet positioned to the current row
|
||||
* @param rowNum The number of the current row being mapped.
|
||||
*/
|
||||
T map(ResultSet resultSet, int rowNum) throws SQLException;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package io.ebean;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> T findOne(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list using the mapper.
|
||||
*
|
||||
* @param mapper Used to map each ResultSet row into the result object.
|
||||
*/
|
||||
<T> List<T> findList(RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* Execute the query reading each row from ResultSet using the RowConsumer.
|
||||
* <p>
|
||||
* This provides a low level option that reads directly from the JDBC ResultSet
|
||||
* and is good for processing very large results where (unlike findList) we don't
|
||||
* hold all the results in memory but instead can process row by row.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "select id, name, status from customer order by name desc";
|
||||
*
|
||||
* Ebean.createSqlQuery(sql)
|
||||
* .findEachRow((resultSet, rowNum) -> {
|
||||
*
|
||||
* // read directly from ResultSet
|
||||
*
|
||||
* long id = resultSet.getLong(1);
|
||||
* String name = resultSet.getString(2);
|
||||
*
|
||||
* // do something interesting with the data
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer Used to read and process each ResultSet row.
|
||||
*/
|
||||
void findEachRow(RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single scalar attribute.
|
||||
* <pre>@{code
|
||||
*
|
||||
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
|
||||
*
|
||||
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 2)
|
||||
* .findSingleAttribute(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> T findSingleAttribute(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single BigDecimal value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
BigDecimal findSingleDecimal();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single Long value.
|
||||
* <p>
|
||||
* This is an alias for <code>findSingleAttribute(Long.class)</code>
|
||||
* </p>
|
||||
*/
|
||||
Long findSingleLong();
|
||||
|
||||
/**
|
||||
* Execute the query returning a list of scalar attribute values.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql =
|
||||
* " select (unit_price * order_qty) " +
|
||||
* " from o_order_detail " +
|
||||
* " where unit_price > ? " +
|
||||
* " order by (unit_price * order_qty) desc";
|
||||
*
|
||||
* //
|
||||
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
|
||||
* .setParameter(1, 3)
|
||||
* .findSingleAttributeList(BigDecimal.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
|
||||
* </p>
|
||||
*
|
||||
* @param attributeType The type of the returned value
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(Class<T> attributeType);
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
@@ -115,6 +222,11 @@ public interface SqlQuery extends Serializable {
|
||||
*/
|
||||
SqlQuery setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set a label that can be put on performance metrics that are collected.
|
||||
*/
|
||||
SqlQuery setLabel(String label);
|
||||
|
||||
/**
|
||||
* A hint which for JDBC translates to the Statement.fetchSize().
|
||||
* <p>
|
||||
|
||||
@@ -19,15 +19,73 @@ package io.ebean;
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // example that uses 'named' parameters
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
* // example that uses 'named' parameters
|
||||
*
|
||||
* int modifiedCount = Ebean.execute(update);
|
||||
* String s = "UPDATE f_topic set post_count = :count where id = :id";
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
* SqlUpdate update = Ebean.createSqlUpdate(s);
|
||||
* update.setParameter("id", 1);
|
||||
* update.setParameter("count", 50);
|
||||
*
|
||||
* int modifiedCount = update.execute();
|
||||
*
|
||||
* String msg = "There were " + modifiedCount + " rows updated";
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Example: Using setNextParameter()</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
|
||||
* SqlUpdate insert = Ebean.createSqlUpdate(sql);
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
* txn.setBatchMode(true);
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.execute();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.execute();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* try (Transaction txn = Ebean.beginTransaction()) {
|
||||
*
|
||||
* insert.setNextParameter(10000);
|
||||
* insert.setNextParameter("hello");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10001);
|
||||
* insert.setNextParameter("goodbye");
|
||||
* insert.setNextParameter("rob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* insert.setNextParameter(10002);
|
||||
* insert.setNextParameter("chow");
|
||||
* insert.setNextParameter("bob");
|
||||
* insert.addBatch();
|
||||
*
|
||||
* int[] rows = insert.executeBatch();
|
||||
*
|
||||
* txn.commit();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -54,6 +112,18 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
* @return The row counts for each of the batched statements.
|
||||
*/
|
||||
int[] executeBatch();
|
||||
|
||||
/**
|
||||
* Add the statement to batch processing to then later execute via executeBatch().
|
||||
*/
|
||||
void addBatch();
|
||||
|
||||
/**
|
||||
* Return the generated key value.
|
||||
*/
|
||||
@@ -61,7 +131,7 @@ public interface SqlUpdate {
|
||||
|
||||
/**
|
||||
* Execute and return the generated key. This is effectively a short cut for:
|
||||
*
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* sqlUpdate.execute();
|
||||
@@ -137,6 +207,13 @@ public interface SqlUpdate {
|
||||
*/
|
||||
SqlUpdate setTimeout(int secs);
|
||||
|
||||
/**
|
||||
* Set the next positioned parameter.
|
||||
*
|
||||
* @param value The value to bind
|
||||
*/
|
||||
SqlUpdate setNextParameter(Object value);
|
||||
|
||||
/**
|
||||
* Set a parameter via its index position.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* This returns the current transaction for the 'default server'. If you are using
|
||||
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
|
||||
* </p>
|
||||
*
|
||||
* @see Ebean#currentTransaction()
|
||||
* @see EbeanServer#currentTransaction()
|
||||
*/
|
||||
@@ -136,6 +137,18 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void rollback(Throwable e) throws PersistenceException;
|
||||
|
||||
/**
|
||||
* Set when we want nested transactions to use Savepoint's.
|
||||
* <p>
|
||||
* This means that for a nested transaction:
|
||||
* <ul>
|
||||
* <li>begin transaction maps to creating a savepoint</li>
|
||||
* <li>commit transaction maps to releasing a savepoint</li>
|
||||
* <li>rollback transaction maps to rollback a savepoint</li>
|
||||
* </ul>
|
||||
*/
|
||||
void setNestedUseSavepoint();
|
||||
|
||||
/**
|
||||
* Mark the transaction for rollback only.
|
||||
*/
|
||||
@@ -151,7 +164,6 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void end();
|
||||
|
||||
|
||||
/**
|
||||
* Synonym for end() to support AutoClosable.
|
||||
*/
|
||||
@@ -227,7 +239,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
|
||||
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // assume Customer has L2 bean caching enabled ...
|
||||
@@ -309,7 +321,7 @@ public interface Transaction extends AutoCloseable {
|
||||
* <p>
|
||||
* Example: batch processing executing every 3 rows
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* String data = "This is a simple test of the batch processing"
|
||||
@@ -347,7 +359,10 @@ public interface Transaction extends AutoCloseable {
|
||||
void setBatchMode(boolean useBatch);
|
||||
|
||||
/**
|
||||
* The JDBC batch mode to use for this transaction.
|
||||
* Deprecated - migrate to {@link #setBatchMode(boolean)}.
|
||||
* <p>
|
||||
* Set the JDBC batch mode to use for this transaction.
|
||||
* </p>
|
||||
* <p>
|
||||
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
|
||||
* and this would be useful if the request cascades to detail beans.
|
||||
@@ -356,15 +371,23 @@ public interface Transaction extends AutoCloseable {
|
||||
* @param persistBatchMode the batch mode to use for this transaction
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatch(PersistBatch persistBatchMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatch();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the transaction level.
|
||||
*/
|
||||
PersistBatch getBatch();
|
||||
boolean isBatchMode();
|
||||
|
||||
/**
|
||||
* Set the JDBC batch mode to use for a save() or delete() request.
|
||||
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
|
||||
* <p>
|
||||
* This only takes effect when batch mode on the transaction has not already meant that
|
||||
* JDBC batch mode is being used.
|
||||
@@ -373,17 +396,36 @@ public interface Transaction extends AutoCloseable {
|
||||
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
|
||||
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is effectively already turned on for all platforms apart from older Sql Server.
|
||||
* </p>
|
||||
*
|
||||
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
|
||||
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
|
||||
*/
|
||||
void setBatchOnCascade(boolean batchMode);
|
||||
|
||||
/**
|
||||
* Set the batch mode when cascading.
|
||||
* <p>
|
||||
* Deprecated in favour of {@link #setBatchOnCascade(boolean)}
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to {@link #isBatchMode()}.
|
||||
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
|
||||
*/
|
||||
@Deprecated
|
||||
PersistBatch getBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Return the batch mode at the request level.
|
||||
*/
|
||||
boolean isBatchOnCascade();
|
||||
|
||||
/**
|
||||
* Specify the number of statements before a batch is flushed automatically.
|
||||
*/
|
||||
|
||||
@@ -152,6 +152,11 @@ public interface Update<T> {
|
||||
*/
|
||||
Update<T> setNullParameter(String name, int jdbcType);
|
||||
|
||||
/**
|
||||
* Set a label meaning performance metrics will be collected for the execution of this update.
|
||||
*/
|
||||
Update<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return the sql that is actually executed.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiFetchGroupService;
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
@@ -15,6 +16,16 @@ class XServiceProvider {
|
||||
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
|
||||
|
||||
private static SpiFetchGroupService initSpiFetchGroupService() {
|
||||
Iterator<SpiFetchGroupService> loader = ServiceLoader.load(SpiFetchGroupService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiFetchGroupService?");
|
||||
}
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
@@ -47,4 +58,17 @@ class XServiceProvider {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
|
||||
return fetchGroupService.of(cls, select);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the FetchGroupBuilder with the given select clause.
|
||||
*/
|
||||
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
|
||||
return fetchGroupService.of(cls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
boolean isSkipSave();
|
||||
|
||||
/**
|
||||
* Return true if the collection holds modifications.
|
||||
*/
|
||||
boolean holdsModifications();
|
||||
|
||||
/**
|
||||
* Return the bean that owns this collection.
|
||||
*/
|
||||
|
||||
@@ -52,6 +52,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private int state;
|
||||
|
||||
private boolean forceUpdate;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
private boolean dirty;
|
||||
@@ -131,6 +133,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.nodeUsageCollector = usageCollector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ownerId (IdClass).
|
||||
*/
|
||||
public Object getOwnerId() {
|
||||
return ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ownerId (IdClass).
|
||||
*/
|
||||
public void setOwnerId(Object ownerId) {
|
||||
this.ownerId = ownerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the owning bean for an embedded bean.
|
||||
*/
|
||||
@@ -292,6 +308,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean to be updated when persisted (for merge).
|
||||
*/
|
||||
public void setForceUpdate(boolean forceUpdate) {
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity should be updated.
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return forceUpdate || state == STATE_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity has been loaded.
|
||||
*/
|
||||
@@ -529,6 +559,16 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set old value but force it to be set regardless if it already has a value.
|
||||
*/
|
||||
private void setOriginalValueForce(int propertyIndex, Object value) {
|
||||
if (origValues == null) {
|
||||
origValues = new Object[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
origValues[propertyIndex] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* For forced update on a 'New' bean set all the loaded properties to changed.
|
||||
*/
|
||||
@@ -901,15 +941,19 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
if (setDirtyState) {
|
||||
setOriginalValue(propertyIndex, origValue);
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
setDirtyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
private void setDirtyStatus() {
|
||||
if (!dirty) {
|
||||
dirty = true;
|
||||
if (embeddedOwner != null) {
|
||||
// Cascade dirty state from Embedded bean to parent bean
|
||||
embeddedOwner._ebean_getIntercept().setEmbeddedDirty(embeddedOwnerIndex);
|
||||
}
|
||||
if (nodeUsageCollector != null) {
|
||||
nodeUsageCollector.setModified();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1049,10 +1093,12 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set an old value.
|
||||
* Explicitly set an old value with force (the old value is forced even it is already set).
|
||||
*/
|
||||
public void setOldValue(int propertyIndex, Object oldValue) {
|
||||
setChangedPropertyValue(propertyIndex, true, oldValue);
|
||||
setChangedProperty(propertyIndex);
|
||||
setOriginalValueForce(propertyIndex, oldValue);
|
||||
setDirtyStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
|
||||
* <p>
|
||||
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
|
||||
* modified since the query cache entry was cached. If any dependent tables have since been modified
|
||||
* the query cache entry is treated as invalid.
|
||||
* </p>
|
||||
*/
|
||||
public class QueryCacheEntry {
|
||||
|
||||
private final Object value;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final long timestamp;
|
||||
|
||||
/**
|
||||
* Create with dependent tables and timestamp.
|
||||
*
|
||||
* @param value The query result being cached
|
||||
* @param dependentTables The extra tables the query is dependent on (joins to)
|
||||
* @param timestamp The timestamp that the query uses to check for modifications
|
||||
*/
|
||||
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
|
||||
this.value = value;
|
||||
this.dependentTables = dependentTables;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual query result.
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tables the query result is dependent on.
|
||||
*/
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp used to check for modifications on the dependent tables.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Used to validate that a query cache entry is still valid based on dependent tables.
|
||||
*/
|
||||
public interface QueryCacheEntryValidate {
|
||||
|
||||
/**
|
||||
* Return true if the entry is still valid based on dependent tables.
|
||||
*/
|
||||
boolean isValid(QueryCacheEntry queryCacheEntry);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Configuration used to create ServerCache instances.
|
||||
*/
|
||||
public class ServerCacheConfig {
|
||||
|
||||
private final ServerCacheType type;
|
||||
private final String cacheKey;
|
||||
private final ServerCacheOptions cacheOptions;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
private final QueryCacheEntryValidate queryCacheEntryValidate;
|
||||
|
||||
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
|
||||
this.type = type;
|
||||
this.cacheKey = cacheKey;
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.tenantProvider = tenantProvider;
|
||||
this.queryCacheEntryValidate = queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache type.
|
||||
*/
|
||||
public ServerCacheType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tuning options.
|
||||
*/
|
||||
public ServerCacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current tenant provider.
|
||||
*/
|
||||
public CurrentTenantProvider getTenantProvider() {
|
||||
return tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the service that provides validation for query cache entries.
|
||||
*/
|
||||
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
|
||||
return queryCacheEntryValidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the cache is a query cache.
|
||||
*/
|
||||
public boolean isQueryCache() {
|
||||
return type == ServerCacheType.QUERY;
|
||||
}
|
||||
}
|
||||
+10
-3
@@ -1,7 +1,5 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
/**
|
||||
* Defines method for constructing caches for beans and queries.
|
||||
*/
|
||||
@@ -10,6 +8,15 @@ public interface ServerCacheFactory {
|
||||
/**
|
||||
* Create the cache for the given type with options.
|
||||
*/
|
||||
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
|
||||
ServerCache createCache(ServerCacheConfig config);
|
||||
|
||||
/**
|
||||
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
|
||||
* <p>
|
||||
* This is used if a ServerCacheNotifyPlugin is not supplied.
|
||||
* </p>
|
||||
*
|
||||
* @param listener The listener that should be used to process the notification events.
|
||||
*/
|
||||
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Notification event that dependent tables have been modified.
|
||||
* <p>
|
||||
* This is sent to other interested servers (in the cluster).
|
||||
* </p>
|
||||
*/
|
||||
public class ServerCacheNotification {
|
||||
|
||||
private final long modifyTimestamp;
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
|
||||
this.modifyTimestamp = modifyTimestamp;
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
public long getModifyTimestamp() {
|
||||
return modifyTimestamp;
|
||||
}
|
||||
|
||||
public Set<String> getDependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
/**
|
||||
* Interface for both listening to notification changes and sending them to other members of the cluster.
|
||||
*/
|
||||
public interface ServerCacheNotify {
|
||||
|
||||
/**
|
||||
* Notify other server cache members of the table modifications or process the notifications.
|
||||
*/
|
||||
void notify(ServerCacheNotification notification);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
/**
|
||||
* Plugin that provides a ServerCacheNotify implementation.
|
||||
* <p>
|
||||
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
|
||||
* </p>
|
||||
*/
|
||||
public interface ServerCacheNotifyPlugin {
|
||||
|
||||
/**
|
||||
* Create a ServerCacheNotify implementation given the server configuration.
|
||||
*/
|
||||
ServerCacheNotify create(ServerConfig serverConfig);
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -64,7 +63,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.ebeanServerName = loader.getName();
|
||||
this.ownerBean = ownerBean;
|
||||
this.propertyName = propertyName;
|
||||
this.readOnly = ownerBean._ebean_getIntercept().isReadOnly();
|
||||
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,13 +95,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
if (loader == null) {
|
||||
loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName);
|
||||
}
|
||||
if (loader == null) {
|
||||
String msg = "Lazy loading but LazyLoadEbeanServer is null?"
|
||||
+ " The LazyLoadEbeanServer needs to be set after deserialization"
|
||||
+ " to support lazy loading.";
|
||||
throw new PersistenceException(msg);
|
||||
}
|
||||
|
||||
loader.loadMany(this, onlyIds);
|
||||
checkEmptyLazyLoad();
|
||||
}
|
||||
@@ -215,7 +207,8 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
/**
|
||||
* Return true if there are underlying additions or removals.
|
||||
*/
|
||||
boolean holdsModifications() {
|
||||
@Override
|
||||
public boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@ package io.ebean.config;
|
||||
public interface AutoConfigure {
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation.
|
||||
* <p>
|
||||
* Return true if the autoConfiguration applies to this ServerConfig.
|
||||
* Perform configuration for the ServerConfig prior to properties load.
|
||||
*/
|
||||
void configure(ServerConfig serverConfig);
|
||||
void preConfigure(ServerConfig serverConfig);
|
||||
|
||||
/**
|
||||
* Provide some configuration the ServerConfig prior to server creation but after properties have been applied.
|
||||
*/
|
||||
void postConfigure(ServerConfig serverConfig);
|
||||
|
||||
}
|
||||
|
||||
@@ -102,6 +102,11 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected Map<String, String> runPlaceholderMap;
|
||||
|
||||
/**
|
||||
* DB schema used for the migration (and testing).
|
||||
*/
|
||||
protected String dbSchema;
|
||||
|
||||
/**
|
||||
* DB user used to run the DB migration.
|
||||
*/
|
||||
@@ -373,6 +378,33 @@ public class DbMigrationConfig {
|
||||
this.dbPassword = dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB schema to use (for migration, testing etc).
|
||||
*/
|
||||
public String getDbSchema() {
|
||||
String schema = readEnvironment("ddl.migration.schema");
|
||||
if (schema != null) {
|
||||
return schema;
|
||||
}
|
||||
return dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema to use.
|
||||
*/
|
||||
public void setDbSchema(String dbSchema) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Db schema if it hasn't already been defined.
|
||||
*/
|
||||
public void setDefaultDbSchema(String dbSchema) {
|
||||
if (this.dbSchema == null) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should be added to history without running.
|
||||
*/
|
||||
@@ -402,9 +434,20 @@ public class DbMigrationConfig {
|
||||
* You can use placeholders like ${version} or ${timestamp} in properties file.
|
||||
*/
|
||||
public String getDdlHeader() {
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
return ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the header prepended to the DDL.
|
||||
*/
|
||||
public void setDdlHeader(String ddlHeader) {
|
||||
this.ddlHeader = ddlHeader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
@@ -464,6 +507,7 @@ public class DbMigrationConfig {
|
||||
runMigration = properties.getBoolean("migration.run", runMigration);
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
|
||||
dbSchema = properties.get("migration.dbSchema", dbSchema);
|
||||
|
||||
//Do not set user and pass from "datasource.db.username"
|
||||
//There is a null test in MigrationRunner::getConnection to handle this
|
||||
@@ -475,10 +519,6 @@ public class DbMigrationConfig {
|
||||
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
|
||||
dbPassword = properties.get("migration.dbpassword", adminPwd);
|
||||
ddlHeader = properties.get("ddl.header", ddlHeader);
|
||||
if (ddlHeader != null && !ddlHeader.isEmpty()) {
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
|
||||
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,6 +590,7 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setRunPlaceholders(runPlaceholders);
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
runnerConfig.setDbPassword(getDbPassword());
|
||||
runnerConfig.setDbSchema(getDbSchema());
|
||||
runnerConfig.setClassLoader(classLoader);
|
||||
if (patchInsertOn != null) {
|
||||
runnerConfig.setPatchInsertOn(patchInsertOn);
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class DbTypeConfig {
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private ServerConfig.DbUuid dbUuid = ServerConfig.DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public ServerConfig.DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(ServerConfig.DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Configuration for DB types such as UUID, Geometry etc.
|
||||
*/
|
||||
public class PlatformConfig {
|
||||
|
||||
private boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* The database boolean true value (typically either 1, T, or Y).
|
||||
*/
|
||||
private String databaseBooleanTrue;
|
||||
|
||||
/**
|
||||
* The database boolean false value (typically either 0, F or N).
|
||||
*/
|
||||
private String databaseBooleanFalse;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* The Geometry SRID value (default 4326).
|
||||
*/
|
||||
private int geometrySRID = 4326;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private DbUuid dbUuid = DbUuid.AUTO_VARCHAR;
|
||||
|
||||
/**
|
||||
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
|
||||
*/
|
||||
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct with defaults.
|
||||
*/
|
||||
public PlatformConfig() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct based on given config - typically for DbMigration generation with many platforms.
|
||||
*/
|
||||
public PlatformConfig(PlatformConfig platformConfig) {
|
||||
this.databaseBooleanFalse = platformConfig.databaseBooleanFalse;
|
||||
this.databaseBooleanTrue = platformConfig.databaseBooleanTrue;
|
||||
this.databaseSequenceBatchSize = platformConfig.databaseSequenceBatchSize;
|
||||
this.idType = platformConfig.idType;
|
||||
this.geometrySRID = platformConfig.geometrySRID;
|
||||
this.dbUuid = platformConfig.dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value returned is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanTrue() {
|
||||
return databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value to represent TRUE in the database.
|
||||
* <p>
|
||||
* This is used for databases that do not support boolean natively.
|
||||
* </p>
|
||||
* <p>
|
||||
* The value set is either a Integer or a String (e.g. "1", or "T").
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanTrue(String databaseBooleanTrue) {
|
||||
this.databaseBooleanTrue = databaseBooleanTrue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent FALSE in the database.
|
||||
*/
|
||||
public String getDatabaseBooleanFalse() {
|
||||
return databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value used to represent FALSE in the database.
|
||||
*/
|
||||
public void setDatabaseBooleanFalse(String databaseBooleanFalse) {
|
||||
this.databaseBooleanFalse = databaseBooleanFalse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public int getDatabaseSequenceBatchSize() {
|
||||
return databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
this.geometrySRID = geometrySRID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdType to use (or null for the default choice).
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal.class/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String.class/varchar but only for Postgres
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom type mapping that applies to all platforms.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // set the default mapping for BigDecimal/decimal
|
||||
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
|
||||
*
|
||||
* // set the default mapping for String/varchar
|
||||
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param type The DB type this mapping should apply to
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of custom type mappings.
|
||||
*/
|
||||
public List<CustomDbTypeMapping> getCustomTypeMappings() {
|
||||
return customDbTypeMappings;
|
||||
}
|
||||
|
||||
public void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
setDbUuid(dbUuid);
|
||||
}
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
int srid = p.getInt("geometrySRID", 0);
|
||||
if (srid > 0) {
|
||||
setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
// Mapping is specified in the form: BOOLEAN=int(1);BIT=int(1);
|
||||
String mapping = p.get("mapping");
|
||||
if (mapping != null && !mapping.isEmpty()) {
|
||||
Map<String, String> map = StringHelper.delimitedToMap(mapping, ";", "=");
|
||||
for (Entry<String, String> entry : map.entrySet()) {
|
||||
addCustomMapping(DbType.valueOf(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
if (quotedIdentifiers != allQuotedIdentifiers) {
|
||||
// potentially also set to use matching naming convention
|
||||
setAllQuotedIdentifiers(quotedIdentifiers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if native UUID type is preferred.
|
||||
*/
|
||||
public boolean useNativeType() {
|
||||
return nativeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
|
||||
*/
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,23 @@ public class PropertiesWrapper {
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
private final ClassLoadConfig classLoadConfig;
|
||||
|
||||
/**
|
||||
* Construct with a prefix, serverName and properties.
|
||||
*/
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
|
||||
public PropertiesWrapper(String prefix, String serverName, Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this.serverName = serverName;
|
||||
this.prefix = prefix;
|
||||
this.properties = properties;
|
||||
this.classLoadConfig = classLoadConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct without prefix of serverName.
|
||||
*/
|
||||
public PropertiesWrapper(Properties properties) {
|
||||
this(null, null, properties);
|
||||
public PropertiesWrapper(Properties properties, ClassLoadConfig classLoadConfig) {
|
||||
this(null, null, properties, classLoadConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,4 +118,30 @@ public class PropertiesWrapper {
|
||||
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
public <T> T createInstance(Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import io.ebean.util.StringHelper;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.time.Clock;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -57,22 +58,24 @@ import java.util.ServiceLoader;
|
||||
* <pre>{@code
|
||||
*
|
||||
* ServerConfig c = new ServerConfig();
|
||||
* c.setName("db");
|
||||
*
|
||||
* // read the ebean.properties and load
|
||||
* // those settings into this serverConfig object
|
||||
* c.loadFromProperties();
|
||||
*
|
||||
* // add any classes found in the app.data package
|
||||
* c.addPackage("com.myapp.domain");
|
||||
*
|
||||
* // register as the 'Default' server
|
||||
* c.setDefaultServer(true);
|
||||
* // explicitly register the entity beans to avoid classpath scanning
|
||||
* c.addClass(Customer.class);
|
||||
* c.addClass(User.class);
|
||||
*
|
||||
* EbeanServer server = EbeanServerFactory.create(c);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
|
||||
* be used to apply configuration to the ServerConfig.
|
||||
* </p>
|
||||
*
|
||||
* @author emcgreal
|
||||
* @author rbygrave
|
||||
* @see EbeanServerFactory
|
||||
@@ -183,16 +186,6 @@ public class ServerConfig {
|
||||
*/
|
||||
private DatabasePlatform databasePlatform;
|
||||
|
||||
/**
|
||||
* The preferred IdType (to override the default Platform type).
|
||||
*/
|
||||
private IdType idType;
|
||||
|
||||
/**
|
||||
* For DB's using sequences this is the number of sequence values prefetched.
|
||||
*/
|
||||
private int databaseSequenceBatchSize = 20;
|
||||
|
||||
/**
|
||||
* JDBC fetchSize hint when using findList. Defaults to 0 leaving it up to the JDBC driver.
|
||||
*/
|
||||
@@ -303,6 +296,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* Optional - the database schema that should be used to own the tables etc.
|
||||
*/
|
||||
private String dbSchema;
|
||||
|
||||
/**
|
||||
* The db migration config (migration resource path etc).
|
||||
*/
|
||||
@@ -330,18 +328,6 @@ public class ServerConfig {
|
||||
*/
|
||||
private String dataSourceJndiName;
|
||||
|
||||
/**
|
||||
* The database boolean true value (typically either 1, T, or Y).
|
||||
*/
|
||||
private String databaseBooleanTrue;
|
||||
|
||||
/**
|
||||
* The database boolean false value (typically either 0, F or N).
|
||||
*/
|
||||
private String databaseBooleanFalse;
|
||||
|
||||
private boolean allQuotedIdentifiers;
|
||||
|
||||
/**
|
||||
* The naming convention.
|
||||
*/
|
||||
@@ -368,9 +354,9 @@ public class ServerConfig {
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Database type configuration.
|
||||
* Database platform configuration.
|
||||
*/
|
||||
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
|
||||
private PlatformConfig platformConfig = new PlatformConfig();
|
||||
|
||||
/**
|
||||
* The UUID version to use.
|
||||
@@ -382,6 +368,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
private List<IdGenerator> idGenerators = new ArrayList<>();
|
||||
private List<BeanFindController> findControllers = new ArrayList<>();
|
||||
private List<BeanPersistController> persistControllers = new ArrayList<>();
|
||||
@@ -510,6 +501,16 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean defaultOrderById = false;
|
||||
|
||||
/**
|
||||
* The mappingLocations for searching xml mapping.
|
||||
*/
|
||||
private List<String> mappingLocations = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* When true we do not need explicit GeneratedValue mapping.
|
||||
*/
|
||||
private boolean idGeneratorAutomatic = true;
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -517,6 +518,20 @@ public class ServerConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public Clock getClock() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
*/
|
||||
public void setClock(final Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query time in millis.
|
||||
*/
|
||||
@@ -946,7 +961,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1110,6 +1125,25 @@ public class ServerConfig {
|
||||
this.profilingConfig = profilingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB schema to use.
|
||||
*/
|
||||
public String getDbSchema() {
|
||||
return dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB schema to use. This specifies to use this schema for:
|
||||
* <ul>
|
||||
* <li>Running Database migrations - Create and use the DB schema</li>
|
||||
* <li>Testing DDL - Create-all.sql DDL execution creates and uses schema</li>
|
||||
* <li>Testing Docker - Set default schema on connection URL</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void setDbSchema(String dbSchema) {
|
||||
this.dbSchema = dbSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB migration configuration.
|
||||
*/
|
||||
@@ -1128,14 +1162,14 @@ public class ServerConfig {
|
||||
* Return the Geometry SRID.
|
||||
*/
|
||||
public int getGeometrySRID() {
|
||||
return dbTypeConfig.getGeometrySRID();
|
||||
return platformConfig.getGeometrySRID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Geometry SRID.
|
||||
*/
|
||||
public void setGeometrySRID(int geometrySRID) {
|
||||
dbTypeConfig.setGeometrySRID(geometrySRID);
|
||||
platformConfig.setGeometrySRID(geometrySRID);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1425,15 +1459,21 @@ public class ServerConfig {
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return allQuotedIdentifiers;
|
||||
return platformConfig.isAllQuotedIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
if (allQuotedIdentifiers && namingConvention instanceof UnderscoreNamingConvention) {
|
||||
platformConfig.setAllQuotedIdentifiers(allQuotedIdentifiers);
|
||||
if (allQuotedIdentifiers) {
|
||||
adjustNamingConventionForAllQuoted();
|
||||
}
|
||||
}
|
||||
|
||||
private void adjustNamingConventionForAllQuoted() {
|
||||
if (namingConvention instanceof UnderscoreNamingConvention) {
|
||||
// we need to use matching naming convention
|
||||
this.namingConvention = new MatchingNamingConvention();
|
||||
}
|
||||
@@ -1648,7 +1688,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanTrue() {
|
||||
return databaseBooleanTrue;
|
||||
return platformConfig.getDatabaseBooleanTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1661,7 +1701,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanTrue(String databaseTrue) {
|
||||
this.databaseBooleanTrue = databaseTrue;
|
||||
platformConfig.setDatabaseBooleanTrue(databaseTrue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1674,7 +1714,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public String getDatabaseBooleanFalse() {
|
||||
return databaseBooleanFalse;
|
||||
return platformConfig.getDatabaseBooleanFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1687,14 +1727,14 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseBooleanFalse(String databaseFalse) {
|
||||
this.databaseBooleanFalse = databaseFalse;
|
||||
this.platformConfig.setDatabaseBooleanFalse(databaseFalse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of DB sequence values that should be preallocated.
|
||||
*/
|
||||
public int getDatabaseSequenceBatchSize() {
|
||||
return databaseSequenceBatchSize;
|
||||
return platformConfig.getDatabaseSequenceBatchSize();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1713,7 +1753,7 @@ public class ServerConfig {
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabaseSequenceBatch(int databaseSequenceBatchSize) {
|
||||
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
|
||||
this.platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1740,7 +1780,7 @@ public class ServerConfig {
|
||||
* that you don't have access to.
|
||||
* </p>
|
||||
* <p>
|
||||
* Values are oracle, h2, postgres, mysql, mssqlserver2005.
|
||||
* Values are oracle, h2, postgres, mysql, sqlserver16, sqlserver17.
|
||||
* </p>
|
||||
*/
|
||||
public void setDatabasePlatformName(String databasePlatformName) {
|
||||
@@ -1769,14 +1809,14 @@ public class ServerConfig {
|
||||
* Return the preferred DB platform IdType.
|
||||
*/
|
||||
public IdType getIdType() {
|
||||
return idType;
|
||||
return platformConfig.getIdType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the preferred DB platform IdType.
|
||||
*/
|
||||
public void setIdType(IdType idType) {
|
||||
this.idType = idType;
|
||||
this.platformConfig.setIdType(idType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1889,15 +1929,22 @@ public class ServerConfig {
|
||||
/**
|
||||
* Return the configuration for DB types (such as UUID and custom mappings).
|
||||
*/
|
||||
public DbTypeConfig getDbTypeConfig() {
|
||||
return dbTypeConfig;
|
||||
public PlatformConfig getPlatformConfig() {
|
||||
return platformConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for DB platform (such as UUID and custom mappings).
|
||||
*/
|
||||
public void setPlatformConfig(PlatformConfig platformConfig) {
|
||||
this.platformConfig = platformConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbTypeConfig.setDbUuid(dbUuid);
|
||||
public void setDbUuid(PlatformConfig.DbUuid dbUuid) {
|
||||
this.platformConfig.setDbUuid(dbUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1965,6 +2012,16 @@ public class ServerConfig {
|
||||
this.durationWithNanos = durationWithNanos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to run DB migrations on server start.
|
||||
* <p>
|
||||
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
|
||||
* as it is often the only thing we need to configure for migrations.
|
||||
*/
|
||||
public void setRunMigration(boolean runMigration) {
|
||||
migrationConfig.setRunMigration(runMigration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to generate the "create all" DDL on startup.
|
||||
* <p>
|
||||
@@ -2353,7 +2410,7 @@ public class ServerConfig {
|
||||
* @param platform Optionally specify the platform this mapping should apply to.
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
|
||||
dbTypeConfig.addCustomMapping(type, columnDefinition, platform);
|
||||
platformConfig.addCustomMapping(type, columnDefinition, platform);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2373,7 +2430,7 @@ public class ServerConfig {
|
||||
* @param columnDefinition The column definition that should be used
|
||||
*/
|
||||
public void addCustomMapping(DbType type, String columnDefinition) {
|
||||
dbTypeConfig.addCustomMapping(type, columnDefinition);
|
||||
platformConfig.addCustomMapping(type, columnDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2642,7 +2699,8 @@ public class ServerConfig {
|
||||
* Load settings from ebean.properties.
|
||||
*/
|
||||
public void loadFromProperties() {
|
||||
loadFromProperties(PropertiesLoader.load());
|
||||
this.properties = PropertiesLoader.load();
|
||||
configureFromProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2650,20 +2708,32 @@ public class ServerConfig {
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
// keep the properties used for configuration so that these are available for plugins
|
||||
this.properties = properties;
|
||||
autoConfiguration();
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
this.properties = PropertiesLoader.eval(properties);
|
||||
configureFromProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the given properties
|
||||
*/
|
||||
private void configureFromProperties() {
|
||||
List<AutoConfigure> autoConfigures = autoConfiguration();
|
||||
loadSettings(new PropertiesWrapper("ebean", name, properties, classLoadConfig));
|
||||
for (AutoConfigure autoConfigure : autoConfigures) {
|
||||
autoConfigure.postConfigure(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a 'plugin' to provide automatic configuration. Intended for automatic testing
|
||||
* configuration with Docker containers via ebean-test-config.
|
||||
*/
|
||||
private void autoConfiguration() {
|
||||
private List<AutoConfigure> autoConfiguration() {
|
||||
List<AutoConfigure> list = new ArrayList<>();
|
||||
for (AutoConfigure autoConfigure : serviceLoad(AutoConfigure.class)) {
|
||||
autoConfigure.configure(this);
|
||||
autoConfigure.preConfigure(this);
|
||||
list.add(autoConfigure);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2687,34 +2757,6 @@ public class ServerConfig {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param properties the properties
|
||||
* @param pluginType the type of plugin
|
||||
* @param key properties key
|
||||
* @param instance existing instance
|
||||
*/
|
||||
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = properties.get(key, null);
|
||||
return createInstance(pluginType, classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance to use (can be null) for the given plugin.
|
||||
*
|
||||
* @param pluginType the type of plugin
|
||||
* @param classname the implementation class as per properties
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T createInstance(Class<T> pluginType, String classname) {
|
||||
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the data source settings to preserve existing behaviour. IMHO, if someone has set the datasource config already,
|
||||
* they don't want the settings to be reloaded and reset. This allows a descending class to override this behaviour and prevent it
|
||||
@@ -2724,6 +2766,7 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadDataSourceSettings(PropertiesWrapper p) {
|
||||
dataSourceConfig.loadSettings(p.properties, name);
|
||||
readOnlyDataSourceConfig.loadSettings(p.properties, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2745,13 +2788,15 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
dbSchema = p.get("dbSchema", dbSchema);
|
||||
if (dbSchema != null) {
|
||||
migrationConfig.setDefaultDbSchema(dbSchema);
|
||||
}
|
||||
profilingConfig.loadSettings(p, name);
|
||||
migrationConfig.loadSettings(p, name);
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
if (quotedIdentifiers != allQuotedIdentifiers) {
|
||||
// potentially also set to use matching naming convention
|
||||
setAllQuotedIdentifiers(quotedIdentifiers);
|
||||
platformConfig.loadSettings(p);
|
||||
if (platformConfig.isAllQuotedIdentifiers()) {
|
||||
adjustNamingConventionForAllQuoted();
|
||||
}
|
||||
namingConvention = createNamingConvention(p, namingConvention);
|
||||
if (namingConvention != null) {
|
||||
@@ -2772,11 +2817,6 @@ public class ServerConfig {
|
||||
}
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
int srid = p.getInt("geometrySRID", 0);
|
||||
if (srid > 0) {
|
||||
dbTypeConfig.setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
@@ -2787,23 +2827,22 @@ public class ServerConfig {
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
currentUserProvider = p.createInstance(CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = p.createInstance(DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = p.createInstance(EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = p.createInstance(EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = p.createInstance(Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = p.createInstance(DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
dbOffline = p.getBoolean("dbOffline", dbOffline);
|
||||
serverCachePlugin = createInstance(p, ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
|
||||
serverCachePlugin = p.createInstance(ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
|
||||
|
||||
if (packages != null) {
|
||||
String packagesProp = p.get("search.packages", p.get("packages", null));
|
||||
packages = getSearchJarsPackages(packagesProp);
|
||||
}
|
||||
String packagesProp = p.get("search.packages", p.get("packages", null));
|
||||
packages = getSearchList(packagesProp, packages);
|
||||
|
||||
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", collectQueryStatsByNode);
|
||||
collectQueryOrigins = p.getBoolean("collectQueryOrigins", collectQueryOrigins);
|
||||
@@ -2819,7 +2858,6 @@ public class ServerConfig {
|
||||
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
|
||||
}
|
||||
|
||||
idType = p.getEnum(IdType.class, "idType", idType);
|
||||
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
|
||||
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
|
||||
|
||||
@@ -2840,20 +2878,9 @@ public class ServerConfig {
|
||||
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
|
||||
jdbcFetchSizeFindEach = p.getInt("jdbcFetchSizeFindEach", jdbcFetchSizeFindEach);
|
||||
jdbcFetchSizeFindList = p.getInt("jdbcFetchSizeFindList", jdbcFetchSizeFindList);
|
||||
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
|
||||
|
||||
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
|
||||
if (dbUuid != null) {
|
||||
dbTypeConfig.setDbUuid(dbUuid);
|
||||
}
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
dbTypeConfig.setDbUuid(DbUuid.BINARY);
|
||||
}
|
||||
|
||||
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
|
||||
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
|
||||
|
||||
@@ -2889,16 +2916,18 @@ public class ServerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
|
||||
tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
|
||||
tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
|
||||
currentTenantProvider = p.createInstance(CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
|
||||
tenantCatalogProvider = p.createInstance(TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
|
||||
tenantSchemaProvider = p.createInstance(TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
|
||||
tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
|
||||
classes = getClasses(p);
|
||||
|
||||
String mappingsProp = p.get("mappingLocations", null);
|
||||
mappingLocations = getSearchList(mappingsProp, mappingLocations);
|
||||
}
|
||||
|
||||
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
|
||||
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
|
||||
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingConvention", null);
|
||||
return (nc != null) ? nc : namingConvention;
|
||||
}
|
||||
|
||||
@@ -2931,17 +2960,17 @@ public class ServerConfig {
|
||||
return classes;
|
||||
}
|
||||
|
||||
private List<String> getSearchJarsPackages(String searchPackages) {
|
||||
private List<String> getSearchList(String searchNames, List<String> defaultValue) {
|
||||
|
||||
if (searchPackages != null) {
|
||||
String[] entries = StringHelper.splitNames(searchPackages);
|
||||
if (searchNames != null) {
|
||||
String[] entries = StringHelper.splitNames(searchNames);
|
||||
|
||||
List<String> hitList = new ArrayList<>(entries.length);
|
||||
Collections.addAll(hitList, entries);
|
||||
|
||||
return hitList;
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3089,71 +3118,68 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
* Create a new PlatformConfig based of the one held but with overridden properties by reading
|
||||
* properties with the given path and prefix.
|
||||
* <p>
|
||||
* Typically used in Db Migration generation for many platform targets that might have different
|
||||
* configuration for IdType, UUID, quoted identifiers etc.
|
||||
* </p>
|
||||
*
|
||||
* @param propertiesPath The properties path used for loading and setting properties
|
||||
* @param platformPrefix The prefix used for loading and setting properties
|
||||
* @return A copy of the PlatformConfig with overridden properties
|
||||
*/
|
||||
public enum DbUuid {
|
||||
public PlatformConfig newPlatformConfig(String propertiesPath, String platformPrefix) {
|
||||
|
||||
PropertiesWrapper p = new PropertiesWrapper(propertiesPath, platformPrefix, properties, classLoadConfig);
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
|
||||
*/
|
||||
AUTO_VARCHAR(true, false, false),
|
||||
PlatformConfig config = new PlatformConfig(platformConfig);
|
||||
config.loadSettings(p);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
|
||||
*/
|
||||
AUTO_BINARY(true, true, false),
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
|
||||
*/
|
||||
AUTO_BINARY_OPTIMIZED(true, true, true),
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR(40).
|
||||
*/
|
||||
VARCHAR(false, false, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY(false, true, false),
|
||||
|
||||
/**
|
||||
* Store using DB BINARY(16).
|
||||
*/
|
||||
BINARY_OPTIMIZED(false, true, true);
|
||||
|
||||
boolean nativeType;
|
||||
boolean binary;
|
||||
boolean binaryOptimized;
|
||||
|
||||
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
|
||||
this.nativeType = nativeType;
|
||||
this.binary = binary;
|
||||
this.binaryOptimized = binaryOptimized;
|
||||
/**
|
||||
* Add a mapping location to search for xml mapping via class path search.
|
||||
*/
|
||||
public void addMappingLocation(String mappingLocation) {
|
||||
if (mappingLocations == null) {
|
||||
mappingLocations = new ArrayList<>();
|
||||
}
|
||||
mappingLocations.add(mappingLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if native UUID type is preferred.
|
||||
*/
|
||||
public boolean useNativeType() {
|
||||
return nativeType;
|
||||
}
|
||||
/**
|
||||
* Return mapping locations to search for xml mapping via class path search.
|
||||
*/
|
||||
public List<String> getMappingLocations() {
|
||||
return mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
|
||||
*/
|
||||
public boolean useBinary() {
|
||||
return binary;
|
||||
}
|
||||
/**
|
||||
* Set mapping locations to search for xml mapping via class path search.
|
||||
* <p>
|
||||
* This is only used if classes have not been explicitly specified.
|
||||
* </p>
|
||||
*/
|
||||
public void setMappingLocations(List<String> mappingLocations) {
|
||||
this.mappingLocations = mappingLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true, if optimized packing should be used.
|
||||
*/
|
||||
public boolean useBinaryOptimized() {
|
||||
return binaryOptimized;
|
||||
}
|
||||
/**
|
||||
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
|
||||
* Identity or Sequence generated values. When true Id properties are automatically
|
||||
* assigned Identity or Sequence without the GeneratedValue mapping.
|
||||
*/
|
||||
public boolean isIdGeneratorAutomatic() {
|
||||
return idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
|
||||
* mapping before they are assigned Identity or Sequence generation based on platform.
|
||||
*/
|
||||
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
|
||||
* <p>
|
||||
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
|
||||
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
|
||||
* </p>
|
||||
* <p>
|
||||
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
|
||||
* spring specific configuration. When we are not using a DI container we may use this mechanism to
|
||||
* explicitly register the entity beans and avoid classpath scanning.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public class EbeanConfigProvider implements ServerConfigProvider {
|
||||
*
|
||||
* @Override
|
||||
* public void apply(ServerConfig config) {
|
||||
*
|
||||
* // register the entity bean classes explicitly
|
||||
* config.addClass(Customer.class);
|
||||
* config.addClass(User.class);
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface ServerConfigProvider {
|
||||
|
||||
/**
|
||||
* Apply the configuration to the ServerConfig.
|
||||
* <p>
|
||||
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
|
||||
* </p>
|
||||
*/
|
||||
void apply(ServerConfig config);
|
||||
}
|
||||
@@ -112,39 +112,7 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
|
||||
* Convert and return the string to underscore from camel case.
|
||||
*/
|
||||
protected String toUnderscoreFromCamel(String camelCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
if ('_' == c) {
|
||||
// Underscores should just be passed through
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
lastUpper = i;
|
||||
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (forceUpperCase) {
|
||||
ret = ret.toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
return CamelCaseHelper.toUnderscoreFromCamel(camelCase, digitsCompressed, forceUpperCase);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,7 @@ import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.CustomDbTypeMapping;
|
||||
import io.ebean.config.DbTypeConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -17,6 +16,7 @@ import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -215,16 +215,16 @@ public class DatabasePlatform {
|
||||
/**
|
||||
* Configure the platform given the server configuration.
|
||||
*/
|
||||
public void configure(ServerConfig config) {
|
||||
public void configure(PlatformConfig config) {
|
||||
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
|
||||
configureIdType(config.getIdType());
|
||||
configure(config.getDbTypeConfig(), config.isAllQuotedIdentifiers());
|
||||
configure(config, config.isAllQuotedIdentifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure UUID Storage etc based on ServerConfig settings.
|
||||
*/
|
||||
protected void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
|
||||
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
|
||||
this.allQuotedIdentifiers = allQuotedIdentifiers;
|
||||
addGeoTypes(config.getGeometrySRID());
|
||||
configureIdType(config.getIdType());
|
||||
@@ -382,6 +382,13 @@ public class DatabasePlatform {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* So no except for Postgres and CockroachDB.
|
||||
*/
|
||||
public boolean isNativeArrayType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB supports native UUID.
|
||||
*/
|
||||
@@ -518,6 +525,13 @@ public class DatabasePlatform {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally not needed - overridden in CockroachPlatform.
|
||||
*/
|
||||
public boolean isDdlCommitOnCreateIndex() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
@@ -645,6 +659,39 @@ public class DatabasePlatform {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DB schema if it does not exist.
|
||||
*/
|
||||
public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException {
|
||||
if (!schemaExists(dbSchema, connection)) {
|
||||
Statement query = connection.createStatement();
|
||||
try {
|
||||
logger.info("create schema:{}", dbSchema);
|
||||
query.executeUpdate("create schema " + dbSchema);
|
||||
} finally {
|
||||
JdbcClose.close(query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the schema exists.
|
||||
*/
|
||||
public boolean schemaExists(String dbSchema, Connection connection) throws SQLException {
|
||||
ResultSet schemas = connection.getMetaData().getSchemas();
|
||||
try {
|
||||
while (schemas.next()) {
|
||||
String schema = schemas.getString(1);
|
||||
if (schema.equalsIgnoreCase(dbSchema)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JdbcClose.close(schemas);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the table exists.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
@@ -119,8 +119,8 @@ public class DbPlatformTypeMapping {
|
||||
put(DbType.JSONCLOB, JSON_CLOB_PLACEHOLDER);
|
||||
put(DbType.JSONBLOB, JSON_BLOB_PLACEHOLDER);
|
||||
put(DbType.JSONVARCHAR, JSON_VARCHAR_PLACEHOLDER);
|
||||
// use reasonable default of varchar(40) - ideally set via DatabasePlatform.configure(DbTypeConfig)
|
||||
put(DbType.UUID, get(DbType.VARCHAR).withLength(40));
|
||||
// default to native UUID and override on platform configure()
|
||||
put(DbType.UUID, UUID_NATIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,9 +198,9 @@ public class DbPlatformTypeMapping {
|
||||
/**
|
||||
* Map the UUID appropriately based on native DB support and ServerConfig.DbUuid.
|
||||
*/
|
||||
public void config(boolean nativeUuidType, ServerConfig.DbUuid dbUuid) {
|
||||
public void config(boolean nativeUuidType, PlatformConfig.DbUuid dbUuid) {
|
||||
if (nativeUuidType && dbUuid.useNativeType()) {
|
||||
put(DbType.UUID, UUID_NATIVE);
|
||||
// native UUID already set by default
|
||||
} else if (dbUuid.useBinary()) {
|
||||
put(DbType.UUID, get(DbType.BINARY).withLength(16));
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebean.config.dbplatform.cockroach;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
|
||||
/**
|
||||
* CockroachDB based platform.
|
||||
*/
|
||||
public class CockroachPlatform extends PostgresPlatform {
|
||||
|
||||
public CockroachPlatform() {
|
||||
super();
|
||||
this.platform = Platform.COCKROACH;
|
||||
// no like escape clause supported
|
||||
this.likeSpecialCharacters = new char[]{'%', '_'};
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.likeClauseEscaped = "like ?";
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs a commit after create index such that alter table add foreign key ... succeeds.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDdlCommitOnCreateIndex() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -88,6 +88,14 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
return new DbPlatformType("geometry(" + type + "," + srid + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* So we can generate varchar[], int[], uuid[] column definitions and use the associated scalar types.
|
||||
*/
|
||||
@Override
|
||||
public boolean isNativeArrayType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform that has non-UTF8 types (char, varchar, text) and default to Identity rather than Sequence.
|
||||
*/
|
||||
public class SqlServer16Platform extends SqlServerBasePlatform {
|
||||
|
||||
public SqlServer16Platform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER16;
|
||||
// default to use Identity rather than sequences
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
|
||||
// non-utf8 column types
|
||||
dbTypeMap.put(DbType.CHAR, new DbPlatformType("char", 1));
|
||||
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("varchar", 255));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform - NVarchar UTF types and Sequence preference.
|
||||
*/
|
||||
public class SqlServer17Platform extends SqlServerBasePlatform {
|
||||
|
||||
public SqlServer17Platform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER17;
|
||||
this.columnAliasPrefix = null;
|
||||
this.nativeUuidType = true;
|
||||
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("uniqueidentifier", false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package io.ebean.config.dbplatform.sqlserver;
|
||||
/**
|
||||
* SQL Server platform using the older ROW_NUMBER() mechanism.
|
||||
*/
|
||||
public class SqlServer2005Platform extends SqlServerPlatform {
|
||||
public class SqlServer2005Platform extends SqlServer16Platform {
|
||||
|
||||
public SqlServer2005Platform() {
|
||||
this.sqlLimiter = new SqlServer2005SqlLimiter();
|
||||
|
||||
+14
-10
@@ -4,7 +4,7 @@ import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
@@ -16,11 +16,11 @@ import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Microsoft SQL Server platform.
|
||||
* Base Microsoft SQL Server platform - NVarchar UTF types and Sequence preference by default.
|
||||
*/
|
||||
public class SqlServerPlatform extends DatabasePlatform {
|
||||
abstract class SqlServerBasePlatform extends DatabasePlatform {
|
||||
|
||||
public SqlServerPlatform() {
|
||||
SqlServerBasePlatform() {
|
||||
super();
|
||||
this.platform = Platform.SQLSERVER;
|
||||
// disable persistBatchOnCascade mode for
|
||||
@@ -31,7 +31,7 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
this.sqlLimiter = new SqlServerSqlLimiter();
|
||||
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
|
||||
this.historySupport = new SqlServerHistorySupport();
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
@@ -63,22 +63,26 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("numeric", 28));
|
||||
|
||||
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
|
||||
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
|
||||
|
||||
dbTypeMap.put(DbType.DATE, new DbPlatformType("date"));
|
||||
dbTypeMap.put(DbType.TIME, new DbPlatformType("time"));
|
||||
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime2"));
|
||||
|
||||
// UTF8 aware types - overwritten in SqlServer16 platform
|
||||
dbTypeMap.put(DbType.CHAR, new DbPlatformType("nchar", 1));
|
||||
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
|
||||
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
dbTypeMap.put(DbType.CLOB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
|
||||
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
|
||||
|
||||
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
|
||||
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(ServerConfig config) {
|
||||
public void configure(PlatformConfig config) {
|
||||
super.configure(config);
|
||||
if (dbIdentity.getIdType() == IdType.SEQUENCE) {
|
||||
this.persistBatchOnCascade = PersistBatch.ALL;
|
||||
@@ -18,26 +18,26 @@ import java.util.Set;
|
||||
*/
|
||||
class LoadContext {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
|
||||
|
||||
/**
|
||||
* Map we are loading the properties into.
|
||||
*/
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
private final Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Names of resources/files that were loaded.
|
||||
*/
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
private final Set<String> loadedResources = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Return the input stream (maybe null) for the given source.
|
||||
*/
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
InputStream resource(String resourcePath, Loader.Source source) {
|
||||
|
||||
InputStream is = null;
|
||||
if (source == Loader.Source.RESOURCE) {
|
||||
is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
is = resourceStream(resourcePath);
|
||||
if (is != null) {
|
||||
loadedResources.add(resourcePath);
|
||||
}
|
||||
@@ -46,43 +46,52 @@ class LoadContext {
|
||||
if (file.exists()) {
|
||||
try {
|
||||
is = new FileInputStream(file);
|
||||
loadedResources.add("file:"+resourcePath);
|
||||
loadedResources.add("file:" + resourcePath);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return is;
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
private InputStream resourceStream(String resourcePath) {
|
||||
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
|
||||
if (is == null) {
|
||||
// search the module path for top level resource
|
||||
is = ClassLoader.getSystemResourceAsStream(resourcePath);
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property entry.
|
||||
*/
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
void put(String key, String val) {
|
||||
if (val != null) {
|
||||
val = val.trim();
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
map.put(key, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate all the expressions and return as a Properties object.
|
||||
*/
|
||||
Properties eval() {
|
||||
Properties eval() {
|
||||
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
log.info("loaded properties from {}", loadedResources);
|
||||
|
||||
Properties properties = new Properties();
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = PropertyEval.eval(entry.getValue());
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the special properties that can point to an external properties source.
|
||||
|
||||
@@ -16,10 +16,10 @@ import java.util.Properties;
|
||||
*/
|
||||
class Loader {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(Loader.class);
|
||||
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
enum Source {
|
||||
RESOURCE,
|
||||
FILE
|
||||
}
|
||||
|
||||
@@ -27,34 +27,36 @@ class Loader {
|
||||
|
||||
private YamlLoader yamlLoader;
|
||||
|
||||
Loader() {
|
||||
|
||||
try {
|
||||
Class<?> exists = Class.forName("org.yaml.snakeyaml.Yaml");
|
||||
if (exists != null) {
|
||||
yamlLoader = new YamlLoader(loadContext);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
Loader() {
|
||||
String skipYaml = System.getProperty("ebeanSkipYaml");
|
||||
if (!"true".equals(skipYaml)) {
|
||||
try {
|
||||
Class<?> exists = Class.forName("org.yaml.snakeyaml.Yaml");
|
||||
if (exists != null) {
|
||||
yamlLoader = new YamlLoader(loadContext);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration with the expected ordering.
|
||||
*/
|
||||
void load() {
|
||||
void load() {
|
||||
|
||||
loadMain(Source.RESOURCE);
|
||||
// external file configuration overrides the resources configuration
|
||||
loadMain(Source.FILE);
|
||||
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
loadViaSystemProperty();
|
||||
loadViaIndirection();
|
||||
|
||||
// test configuration (if found) overrides main configuration
|
||||
// test configuration (if found) overrides main configuration
|
||||
// we should only find these resources when running tests
|
||||
loadTest();
|
||||
}
|
||||
loadTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load test configuration.
|
||||
@@ -87,7 +89,7 @@ class Loader {
|
||||
}
|
||||
|
||||
private void loadViaSystemProperty() {
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName != null) {
|
||||
@@ -110,55 +112,66 @@ class Loader {
|
||||
* Evaluate all the configuration entries and return as properties.
|
||||
*/
|
||||
Properties eval() {
|
||||
return loadContext.eval();
|
||||
}
|
||||
return loadContext.eval();
|
||||
}
|
||||
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
yamlLoader.load(resource(resourcePath, source));
|
||||
}
|
||||
}
|
||||
void loadYaml(String resourcePath, Source source) {
|
||||
if (yamlLoader != null) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
yamlLoader.load(is);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read yml from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
InputStream is = resource(resourcePath, source);
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
void loadProperties(String resourcePath, Source source) {
|
||||
try {
|
||||
try (InputStream is = resource(resourcePath, source)) {
|
||||
if (is != null) {
|
||||
loadProperties(is);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to read properties from:" + resourcePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
private InputStream resource(String resourcePath, Source source) {
|
||||
return loadContext.resource(resourcePath, source);
|
||||
}
|
||||
|
||||
private void loadProperties(InputStream is) {
|
||||
private void loadProperties(InputStream is) {
|
||||
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is != null) {
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(is);
|
||||
put(properties);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load properties?", e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
private void put(Properties properties) {
|
||||
Enumeration<?> enumeration = properties.propertyNames();
|
||||
while (enumeration.hasMoreElements()) {
|
||||
String key = (String) enumeration.nextElement();
|
||||
String property = properties.getProperty(key);
|
||||
loadContext.put(key, property);
|
||||
}
|
||||
}
|
||||
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
private void close(InputStream is) {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Error closing input stream for properties", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean.config.properties;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
@@ -56,14 +57,18 @@ public class PropertiesLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (override) a configuration property.
|
||||
*
|
||||
* This is expected to be only called by test configuration logic
|
||||
* (ala automatic test configuration via ebean-test-config).
|
||||
* Return a copy of the properties with 'eval' run on all the values.
|
||||
* This resolves expressions like ${HOME} etc.
|
||||
*/
|
||||
public static synchronized void setProperty(String key, String value) {
|
||||
load();
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
public static Properties eval(Properties properties) {
|
||||
Properties evalCopy = new Properties();
|
||||
|
||||
Enumeration<?> names = properties.propertyNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = (String)names.nextElement();
|
||||
String value = PropertyEval.eval(properties.getProperty(name));
|
||||
evalCopy.setProperty(name, value);
|
||||
}
|
||||
return evalCopy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,12 @@ class YamlLoader {
|
||||
this.loadContext = loadContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void load(InputStream is) {
|
||||
if (is != null) {
|
||||
loadMap(yaml.load(is), null);
|
||||
for (Object map : yaml.loadAll(is)) {
|
||||
loadMap((Map<String, Object>)map, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,38 @@ public interface DbMigration {
|
||||
*/
|
||||
void setStrictMode(boolean strictMode);
|
||||
|
||||
/**
|
||||
* Set to true to include a generated header comment in the DDL script.
|
||||
*/
|
||||
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
|
||||
|
||||
/**
|
||||
* Set the header that is included in the generated DDL script.
|
||||
*/
|
||||
void setHeader(String header);
|
||||
|
||||
/**
|
||||
* Set the prefix for the version. Set this to "V" for use with Flyway.
|
||||
*/
|
||||
void setApplyPrefix(String applyPrefix);
|
||||
|
||||
/**
|
||||
* Set the version of the migration to be generated.
|
||||
*/
|
||||
void setVersion(String version);
|
||||
|
||||
/**
|
||||
* Set the name of the migration to be generated.
|
||||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Generate a migration for the version specified that contains pending drops.
|
||||
*
|
||||
* @param generatePendingDrop The version of a prior migration that holds pending drops.
|
||||
*/
|
||||
void setGeneratePendingDrop(String generatePendingDrop);
|
||||
|
||||
/**
|
||||
* Add an additional platform to write the migration DDL.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ebean.event;
|
||||
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
|
||||
/**
|
||||
* Listens for webserver server starting and stopping events.
|
||||
* <p>
|
||||
* This should be used when the deployment is into a servlet container where the webapp
|
||||
* can be shutdown or redeployed without the JVM stopping.
|
||||
* </p>
|
||||
* <p>
|
||||
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
|
||||
* runnable war or when using a servlet container that only contains the single webapp and
|
||||
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
|
||||
* hook that Ebean registers.
|
||||
* </p>
|
||||
*/
|
||||
public class ServletContextListener implements javax.servlet.ServletContextListener {
|
||||
|
||||
/**
|
||||
* The servlet container is stopping.
|
||||
*/
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent event) {
|
||||
ShutdownManager.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing on startup.
|
||||
*/
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent event) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* An abstract MetricVisitor that handles the boolean flags - reset, collectTransactionMetrics and collectQueryMetrics.
|
||||
*/
|
||||
public abstract class AbstractMetricVisitor implements MetricVisitor {
|
||||
|
||||
private final boolean reset;
|
||||
private final boolean collectTransactionMetrics;
|
||||
private final boolean collectQueryMetrics;
|
||||
|
||||
public AbstractMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
|
||||
this.reset = reset;
|
||||
this.collectTransactionMetrics = collectTransactionMetrics;
|
||||
this.collectQueryMetrics = collectQueryMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReset() {
|
||||
return reset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectTransactionMetrics() {
|
||||
return collectTransactionMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectQueryMetrics() {
|
||||
return collectQueryMetrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitStart() {
|
||||
// do nothing by default
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitEnd() {
|
||||
// do nothing by default
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple MetricVisitor that can collect the desired metrics into lists.
|
||||
*/
|
||||
public class BasicMetricVisitor extends AbstractMetricVisitor {
|
||||
|
||||
private final List<MetaTimedMetric> timed = new ArrayList<>();
|
||||
private final List<MetaQueryMetric> dtoQuery = new ArrayList<>();
|
||||
private final List<MetaOrmQueryMetric> ormQuery = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construct to reset and collect everything.
|
||||
*/
|
||||
public BasicMetricVisitor() {
|
||||
super(true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct specifying reset and what to collect.
|
||||
*/
|
||||
public BasicMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
|
||||
super(reset, collectTransactionMetrics, collectQueryMetrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
|
||||
*/
|
||||
public List<MetaTimedMetric> getTimedMetrics() {
|
||||
return timed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DTO query metrics.
|
||||
*/
|
||||
public List<MetaQueryMetric> getDtoQueryMetrics() {
|
||||
return dtoQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ORM query metrics.
|
||||
*/
|
||||
public List<MetaOrmQueryMetric> getOrmQueryMetrics() {
|
||||
return ormQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTimed(MetaTimedMetric metric) {
|
||||
timed.add(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitQuery(MetaQueryMetric metric) {
|
||||
dtoQuery.add(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitOrmQuery(MetaOrmQueryMetric metric) {
|
||||
ormQuery.add(metric);
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,20 @@ import java.util.List;
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Collect and return the transaction execution metrics.
|
||||
* Visit the metrics resetting and collecting/reporting as desired.
|
||||
*/
|
||||
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Collect query plan statistics (new, will migrate ORM query stats over to this).
|
||||
* Run a visit collecting all the metrics and returning BasicMetricVisitor
|
||||
* which holds all the metrics in simple lists.
|
||||
*/
|
||||
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
|
||||
BasicMetricVisitor visitBasic();
|
||||
|
||||
/**
|
||||
* Collect and return the non-empty query plan statistics for all the beans.
|
||||
* <p>
|
||||
* Note that this excludes the query plan statistics where there has been no
|
||||
* executions (since the last collection with reset).
|
||||
* </p>
|
||||
* Just reset all the metrics. Maybe only useful for testing purposes.
|
||||
*/
|
||||
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
|
||||
void resetAllMetrics();
|
||||
|
||||
/**
|
||||
* Collect and return the ObjectGraphNode statistics.
|
||||
@@ -36,6 +33,6 @@ public interface MetaInfoManager {
|
||||
*
|
||||
* @param reset Set to true to reset the underlying statistics after collection.
|
||||
*/
|
||||
List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset);
|
||||
List<MetaOrmQueryNode> collectNodeStatistics(boolean reset);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Query execution statistics for Orm queries.
|
||||
*/
|
||||
public interface MetaOrmQueryMetric extends MetaQueryMetric {
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this query plan was tuned by AutoTune.
|
||||
*/
|
||||
boolean isAutoTuned();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
/**
|
||||
* Return the time of the last query executed using this plan.
|
||||
*/
|
||||
long getLastQueryTime();
|
||||
|
||||
/**
|
||||
* Return the 'origin' points and paths that resulted in the query being
|
||||
* executed and the associated number of times the query was executed via that
|
||||
* path.
|
||||
* <p>
|
||||
* This includes direct and lazy loading paths.
|
||||
* </p>
|
||||
*/
|
||||
List<MetaOrmQueryOrigin> getOrigins();
|
||||
|
||||
}
|
||||
+1
-3
@@ -8,10 +8,8 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
* These statistics can be used to identify origin queries that result in lots
|
||||
* of lazy loading.
|
||||
* </p>
|
||||
*
|
||||
* @see MetaInfoManager#collectNodeStatistics(boolean)
|
||||
*/
|
||||
public interface MetaObjectGraphNodeStats {
|
||||
public interface MetaOrmQueryNode {
|
||||
|
||||
/**
|
||||
* Return the ObjectGraphNode which has the origin point and relative path.
|
||||
+1
-4
@@ -9,11 +9,8 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
* This basically points to the bit of original code and query that results in
|
||||
* this query directly or via lazy loading.
|
||||
* </p>
|
||||
*
|
||||
* @see MetaQueryPlanStatistic
|
||||
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
|
||||
*/
|
||||
public interface MetaQueryPlanOriginCount {
|
||||
public interface MetaOrmQueryOrigin {
|
||||
|
||||
/**
|
||||
* The 'origin' and path which this query belongs to.
|
||||
@@ -1,98 +0,0 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Query execution statistics Meta data.
|
||||
*
|
||||
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
|
||||
*/
|
||||
public interface MetaQueryPlanStatistic {
|
||||
|
||||
/**
|
||||
* Return the bean type this query plan is for.
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this query plan was tuned by AutoTune.
|
||||
*/
|
||||
boolean isAutoTuned();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
/**
|
||||
* Return the sql executed.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
/**
|
||||
* Return the total number of queries executed.
|
||||
*/
|
||||
long getExecutionCount();
|
||||
|
||||
/**
|
||||
* Return the total number of beans loaded by the queries.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getTotalLoadedBeans();
|
||||
|
||||
/**
|
||||
* Return the total time taken by executions of this query.
|
||||
*/
|
||||
long getTotalTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the max execution time for this query.
|
||||
*/
|
||||
long getMaxTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the time collection started (or was last reset).
|
||||
*/
|
||||
long getCollectionStart();
|
||||
|
||||
/**
|
||||
* Return the time of the last query executed using this plan.
|
||||
*/
|
||||
long getLastQueryTime();
|
||||
|
||||
/**
|
||||
* Return the average query execution time in microseconds.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getAvgTimeMicros();
|
||||
|
||||
/**
|
||||
* Return the average number of bean loaded per query.
|
||||
* <p>
|
||||
* This excludes background fetching.
|
||||
* </p>
|
||||
*/
|
||||
long getAvgLoadedBeans();
|
||||
|
||||
/**
|
||||
* Return the 'origin' points and paths that resulted in the query being
|
||||
* executed and the associated number of times the query was executed via that
|
||||
* path.
|
||||
* <p>
|
||||
* This includes direct and lazy loading paths.
|
||||
* </p>
|
||||
*/
|
||||
List<MetaQueryPlanOriginCount> getOrigins();
|
||||
|
||||
}
|
||||
@@ -6,6 +6,11 @@ package io.ebean.meta;
|
||||
*/
|
||||
public interface MetaTimedMetric {
|
||||
|
||||
/**
|
||||
* Return the metric type.
|
||||
*/
|
||||
MetricType getMetricType();
|
||||
|
||||
/**
|
||||
* Return the metric name.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* The type of Metric.
|
||||
*/
|
||||
public enum MetricType {
|
||||
|
||||
/**
|
||||
* Transactions.
|
||||
*/
|
||||
TXN,
|
||||
|
||||
/**
|
||||
* ORM queries.
|
||||
*/
|
||||
ORM,
|
||||
|
||||
/**
|
||||
* DTO queries.
|
||||
*/
|
||||
DTO,
|
||||
|
||||
/**
|
||||
* SQL queries with a label will have metrics collected.
|
||||
* <p>
|
||||
* SqlQuery and SqlUpdate without a label have no metrics collected.
|
||||
*/
|
||||
SQL
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
/**
|
||||
* Defines visitor to read and report the transaction and query metrics.
|
||||
*/
|
||||
public interface MetricVisitor {
|
||||
|
||||
/**
|
||||
* Return true if the metrics should be reset.
|
||||
*/
|
||||
boolean isReset();
|
||||
|
||||
/**
|
||||
* Return true if we should visit the transaction metrics.
|
||||
*/
|
||||
boolean isCollectTransactionMetrics();
|
||||
|
||||
/**
|
||||
* Return true if we should visit the ORM and SQL query metrics.
|
||||
*/
|
||||
boolean isCollectQueryMetrics();
|
||||
|
||||
/**
|
||||
* Visit has started.
|
||||
*/
|
||||
void visitStart();
|
||||
|
||||
/**
|
||||
* Visit transaction metrics (and L2 cache metrics in future).
|
||||
*/
|
||||
void visitTimed(MetaTimedMetric metric);
|
||||
|
||||
/**
|
||||
* Visit DTO and SQL query metrics.
|
||||
*/
|
||||
void visitQuery(MetaQueryMetric metric);
|
||||
|
||||
/**
|
||||
* Visit ORM query metrics.
|
||||
*/
|
||||
void visitOrmQuery(MetaOrmQueryMetric metric);
|
||||
|
||||
/**
|
||||
* Visit has completed.
|
||||
*/
|
||||
void visitEnd();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.FetchGroup;
|
||||
import io.ebean.FetchGroupBuilder;
|
||||
|
||||
/**
|
||||
* Service that parses FetchGroup expressions.
|
||||
*/
|
||||
public interface SpiFetchGroupService {
|
||||
|
||||
/**
|
||||
* Return the FetchGroup with the given select clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @param select The properties to select (top level properties)
|
||||
*/
|
||||
<T> FetchGroup<T> of(Class<T> beanType, String select);
|
||||
|
||||
/**
|
||||
* Create and return a FetchGroupBuilder starting with a select() clause.
|
||||
*
|
||||
* @param beanType The type of entity bean the fetch group is for
|
||||
* @return The FetchGroupBuilder to add additional select and fetch clauses
|
||||
*/
|
||||
<T> FetchGroupBuilder<T> of(Class<T> beanType);
|
||||
}
|
||||
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
|
||||
}
|
||||
|
||||
Props getProps(String path) {
|
||||
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
return props;
|
||||
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
|
||||
}
|
||||
|
||||
public Collection<Props> getPathProps() {
|
||||
|
||||
@@ -2,6 +2,52 @@ package io.ebean.util;
|
||||
|
||||
public class CamelCaseHelper {
|
||||
|
||||
/**
|
||||
* To underscore from camel case using digits compressed true and force upper case false.
|
||||
*/
|
||||
public static String toUnderscoreFromCamel(String camelCase) {
|
||||
return toUnderscoreFromCamel(camelCase, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert and return the string to underscore from camel case.
|
||||
*/
|
||||
public static String toUnderscoreFromCamel(String camelCase, boolean digitsCompressed, boolean forceUpperCase) {
|
||||
|
||||
int lastUpper = -1;
|
||||
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
|
||||
for (int i = 0; i < camelCase.length(); i++) {
|
||||
char c = camelCase.charAt(i);
|
||||
|
||||
if ('_' == c) {
|
||||
// Underscores should just be passed through
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
} else if (Character.isDigit(c)) {
|
||||
if (i > lastUpper + 1 && !digitsCompressed) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(c);
|
||||
lastUpper = i;
|
||||
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
if (i > lastUpper + 1) {
|
||||
sb.append("_");
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
lastUpper = i;
|
||||
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
String ret = sb.toString();
|
||||
if (forceUpperCase) {
|
||||
ret = ret.toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* To camel from underscore.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Context used to read binary format messages.
|
||||
*/
|
||||
public class BinaryReadContext {
|
||||
|
||||
private final DataInputStream in;
|
||||
|
||||
/**
|
||||
* Create with protocol 0 and byte data.
|
||||
*/
|
||||
public BinaryReadContext(byte[] byteData) {
|
||||
this(new DataInputStream(new ByteArrayInputStream(byteData)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with protocol version and DataInputStream data.
|
||||
*/
|
||||
public BinaryReadContext(DataInputStream in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public DataInputStream in() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public boolean readBoolean() throws IOException {
|
||||
return in.readBoolean();
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
return in.readInt();
|
||||
}
|
||||
|
||||
public String readUTF() throws IOException {
|
||||
return in.readUTF();
|
||||
}
|
||||
|
||||
public long readLong() throws IOException {
|
||||
return in.readLong();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Messages that can be sent in binary form.
|
||||
* <p>
|
||||
* Mainly RemoteTransactionEvent which is sent to cluster members.
|
||||
* </p>
|
||||
*/
|
||||
public interface BinaryWritable {
|
||||
|
||||
int TYPE_BEANIUD = 1;
|
||||
int TYPE_TABLEIUD = 2;
|
||||
int TYPE_CACHE = 3;
|
||||
int TYPE_TABLEMOD = 4;
|
||||
|
||||
/**
|
||||
* Write message in binary format.
|
||||
*/
|
||||
void writeBinary(BinaryWriteContext out) throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Context used to write binary message (like RemoteTransactionEvent).
|
||||
*/
|
||||
public class BinaryWriteContext {
|
||||
|
||||
private final DataOutputStream out;
|
||||
|
||||
private long counter;
|
||||
|
||||
public BinaryWriteContext(DataOutputStream out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of message parts that have been written.
|
||||
*/
|
||||
public long counter() {
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the output stream to write to.
|
||||
*/
|
||||
public DataOutputStream os() {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a message part with a given type code.
|
||||
*/
|
||||
public DataOutputStream start(int type) throws IOException {
|
||||
counter++;
|
||||
out.writeBoolean(true);
|
||||
out.writeInt(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* End of message parts.
|
||||
*/
|
||||
public void end() throws IOException {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -154,6 +155,10 @@ public class BindParams implements Serializable {
|
||||
public void setParameter(int position, Object value) {
|
||||
|
||||
Param p = getParam(position);
|
||||
if (value instanceof Collection) {
|
||||
// use of postgres ANY with positioned parameter
|
||||
value = new MultiValueWrapper((Collection)value);
|
||||
}
|
||||
p.setInValue(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.util.StringHelper;
|
||||
@@ -124,11 +123,11 @@ public class LoadManyRequest extends LoadRequest {
|
||||
return loadContext.getBeanProperty();
|
||||
}
|
||||
|
||||
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
|
||||
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
|
||||
|
||||
BeanPropertyAssocMany<?> many = getMany();
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
|
||||
SpiQuery<?> query = many.newQuery(server);
|
||||
String orderBy = many.getLazyFetchOrderBy();
|
||||
if (orderBy != null) {
|
||||
query.orderBy(orderBy);
|
||||
|
||||
@@ -53,6 +53,10 @@ public class ScopeTrans {
|
||||
*/
|
||||
private boolean rolledBack;
|
||||
|
||||
/**
|
||||
* Flag set when nested commit has occurred.
|
||||
*/
|
||||
private boolean nestedCommit;
|
||||
|
||||
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
|
||||
|
||||
@@ -113,7 +117,6 @@ public class ScopeTrans {
|
||||
complete();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Complete the transaction programmatically. Try to commit.
|
||||
*/
|
||||
@@ -124,7 +127,7 @@ public class ScopeTrans {
|
||||
}
|
||||
|
||||
public void end() {
|
||||
if (created) {
|
||||
if (created || !nestedCommit) {
|
||||
transaction.end();
|
||||
}
|
||||
}
|
||||
@@ -133,6 +136,7 @@ public class ScopeTrans {
|
||||
if (created) {
|
||||
transaction.commit();
|
||||
} else {
|
||||
nestedCommit = true;
|
||||
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
|
||||
if (restoreBatch != null) {
|
||||
transaction.setBatch(restoreBatch);
|
||||
|
||||
@@ -21,6 +21,11 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
|
||||
private ScopeTrans current;
|
||||
|
||||
/**
|
||||
* Flag set when we clear the thread scope (on commit/rollback or end).
|
||||
*/
|
||||
private boolean scopeCleared;
|
||||
|
||||
public ScopedTransaction(TransactionScopeManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
@@ -47,30 +52,51 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
*/
|
||||
public void complete(Object returnOrThrowable, int opCode) {
|
||||
current.complete(returnOrThrowable, opCode);
|
||||
// no finally here for pop() as we come in here twice if an
|
||||
// error is thrown on commit (due to enhancement finally block)
|
||||
pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Programmatic complete - finally block, try to commit.
|
||||
* Internal programmatic complete - finally block, try to commit.
|
||||
*/
|
||||
public void complete() {
|
||||
current.complete();
|
||||
pop();
|
||||
try {
|
||||
current.complete();
|
||||
} finally {
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearScopeOnce() {
|
||||
if (!scopeCleared) {
|
||||
manager.set(null);
|
||||
scopeCleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clearScope() {
|
||||
if (stack.isEmpty()) {
|
||||
clearScopeOnce();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void pop() {
|
||||
if (!stack.isEmpty()) {
|
||||
if (!clearScope()) {
|
||||
current = stack.pop();
|
||||
transaction = current.getTransaction();
|
||||
} else {
|
||||
manager.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() throws PersistenceException {
|
||||
current.end();
|
||||
pop();
|
||||
try {
|
||||
current.end();
|
||||
} finally {
|
||||
pop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,17 +106,29 @@ public class ScopedTransaction extends SpiTransactionProxy {
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
current.commitTransaction();
|
||||
try {
|
||||
current.commitTransaction();
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws PersistenceException {
|
||||
current.rollback(null);
|
||||
try {
|
||||
current.rollback(null);
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(Throwable e) throws PersistenceException {
|
||||
current.rollback(e);
|
||||
try {
|
||||
current.rollback(e);
|
||||
} finally {
|
||||
clearScope();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Plugin API available to invoke something prior to container bootup.
|
||||
* <p>
|
||||
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
|
||||
* we have a test DB docker container running and setup ready to go.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiContainerBootup {
|
||||
|
||||
/**
|
||||
* Run something at bootup prior to the container starting.
|
||||
*
|
||||
* For example, start DB docker container(s).
|
||||
*/
|
||||
void bootup();
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
/**
|
||||
* Get the query plan for the cache.
|
||||
*/
|
||||
DtoQueryPlan getQueryPlan(String planKey);
|
||||
DtoQueryPlan getQueryPlan(Object planKey);
|
||||
|
||||
/**
|
||||
* Build the query plan.
|
||||
@@ -27,7 +27,7 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
/**
|
||||
* Put the query plan into the cache.
|
||||
*/
|
||||
void putQueryPlan(String planKey, DtoQueryPlan plan);
|
||||
void putQueryPlan(Object planKey, DtoQueryPlan plan);
|
||||
|
||||
/**
|
||||
* Return true if the query is in relaxed mapping mode.
|
||||
@@ -43,4 +43,10 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
* Return the associated DTO bean type.
|
||||
*/
|
||||
Class<T> getType();
|
||||
|
||||
/**
|
||||
* Return an underlying ORM query (if this query is built from an ORM query).
|
||||
*/
|
||||
SpiQuery<?> getOrmQuery();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.DtoQuery;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.ExtendedServer;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
@@ -13,7 +17,9 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.server.core.SpiResultSet;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQuery;
|
||||
@@ -26,7 +32,12 @@ import java.util.function.Predicate;
|
||||
/**
|
||||
* Service Provider extension to EbeanServer.
|
||||
*/
|
||||
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
|
||||
/**
|
||||
* Return the log manager.
|
||||
*/
|
||||
SpiLogManager log();
|
||||
|
||||
/**
|
||||
* Return the server extended Json context.
|
||||
@@ -226,6 +237,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
void scopedTransactionExit(Object returnOrThrowable, int opCode);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute.
|
||||
*/
|
||||
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find single attribute list.
|
||||
*/
|
||||
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
|
||||
|
||||
/**
|
||||
* SqlQuery find one with mapper.
|
||||
*/
|
||||
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find list with mapper.
|
||||
*/
|
||||
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
|
||||
|
||||
/**
|
||||
* SqlQuery find each with consumer.
|
||||
*/
|
||||
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* DTO findList query.
|
||||
*/
|
||||
@@ -246,4 +282,34 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
*/
|
||||
<T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Return / wrap the ORM query as a DTO query.
|
||||
*/
|
||||
<D> DtoQuery<D> findDto(Class<D> dtoType, SpiQuery<?> ormQuery);
|
||||
|
||||
/**
|
||||
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
|
||||
*/
|
||||
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Visit all the metrics (typically reporting them).
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Return true if a row for the bean type and id exists.
|
||||
*/
|
||||
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Add to JDBC batch for later execution.
|
||||
*/
|
||||
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the batched statement.
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.core.DbExpressionHandler;
|
||||
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.text.json.JsonWriteOptions;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
@@ -21,4 +23,10 @@ public interface SpiJsonContext extends JsonContext {
|
||||
* Create a Json Writer for writing beans as JSON supplying a writer.
|
||||
*/
|
||||
SpiJsonWriter createJsonWriter(Writer writer);
|
||||
|
||||
/**
|
||||
* Create a Json Reader for reading the JSON content.
|
||||
*/
|
||||
SpiJsonReader createJsonRead(BeanType<?> beanType, String json);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Log manager for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogManager {
|
||||
|
||||
/**
|
||||
* Return the SQL logger.
|
||||
*/
|
||||
SpiLogger sql();
|
||||
|
||||
/**
|
||||
* Return the TXN logger.
|
||||
*/
|
||||
SpiLogger txn();
|
||||
|
||||
/**
|
||||
* Return the Summary logger.
|
||||
*/
|
||||
SpiLogger sum();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Logger for SQL, TXN and Summary logging.
|
||||
* <p>
|
||||
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
|
||||
* the logged SQL during testing such that we can assert against the executed sql if desired.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiLogger {
|
||||
|
||||
/**
|
||||
* Is debug logging enabled.
|
||||
*/
|
||||
boolean isDebug();
|
||||
|
||||
/**
|
||||
* Is trace logging enabled.
|
||||
*/
|
||||
boolean isTrace();
|
||||
|
||||
/**
|
||||
* Log a debug level message.
|
||||
*/
|
||||
void debug(String msg);
|
||||
|
||||
/**
|
||||
* Log a trace level message.
|
||||
*/
|
||||
void trace(String msg);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
|
||||
*/
|
||||
public interface SpiLoggerFactory {
|
||||
|
||||
/**
|
||||
* Create the logger given the name.
|
||||
*/
|
||||
SpiLogger create(String name);
|
||||
}
|
||||
@@ -179,6 +179,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return the label set on the query.
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
|
||||
*/
|
||||
@@ -215,6 +220,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
BeanDescriptor<T> getBeanDescriptor();
|
||||
|
||||
/**
|
||||
* Return the query plan key.
|
||||
*/
|
||||
Object getQueryPlanKey();
|
||||
|
||||
/**
|
||||
* Return the RawSql that was set to use for this query.
|
||||
*/
|
||||
@@ -634,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
boolean isBeanCachePut();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
|
||||
* Return true if we must hit the DB (Cache reload or select for update).
|
||||
*/
|
||||
boolean isBeanCacheReload();
|
||||
boolean isForceHitDatabase();
|
||||
|
||||
/**
|
||||
* Return the cache mode for using the bean cache (Get and Put).
|
||||
@@ -748,6 +758,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isDistinct();
|
||||
|
||||
/**
|
||||
* Return true if the Id property is manually included in the query (DTO queries).
|
||||
*/
|
||||
boolean isManualId();
|
||||
|
||||
/**
|
||||
* Set to true when we only include the Id property if it is explicitly included in the select().
|
||||
*/
|
||||
void setManualId(boolean manualId);
|
||||
|
||||
/**
|
||||
* Set default select clauses where none have been explicitly defined.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,11 @@ public interface SpiSqlBinding {
|
||||
*/
|
||||
String getQuery();
|
||||
|
||||
/**
|
||||
* Return the label (to collect metrics on when set).
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the first row to fetch.
|
||||
*/
|
||||
|
||||
@@ -23,4 +23,9 @@ public interface SpiSqlUpdate extends SqlUpdate {
|
||||
* Set the generated key value.
|
||||
*/
|
||||
void setGeneratedKey(Object idValue);
|
||||
|
||||
/**
|
||||
* Reset bind position to be ready for another bind execute.
|
||||
*/
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return the start timestamp for the transaction (JVM side).
|
||||
*/
|
||||
long getStartMillis();
|
||||
|
||||
/**
|
||||
* Return true if this transaction has updateAllLoadedProperties set.
|
||||
* If null is returned the server default is used (set on ServerConfig).
|
||||
@@ -179,7 +184,7 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return true if this request should be batched. Conversely returns false
|
||||
* if this request should be executed immediately.
|
||||
*/
|
||||
boolean isBatchThisRequest(PersistRequest.Type type);
|
||||
boolean isBatchThisRequest();
|
||||
|
||||
/**
|
||||
* Return the BatchControl used to batch up persist requests.
|
||||
@@ -319,4 +324,9 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return the profile location for this transaction.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true when nested transactions should create Savepoints.
|
||||
*/
|
||||
boolean isNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.sql.SQLException;
|
||||
/**
|
||||
* Proxy for an underlying SpiTransaction (most of the API).
|
||||
*/
|
||||
abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
|
||||
protected SpiTransaction transaction;
|
||||
|
||||
@@ -30,6 +30,11 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.translate(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartMillis() {
|
||||
return transaction.getStartMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLabel(String label) {
|
||||
transaction.setLabel(label);
|
||||
@@ -50,6 +55,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNestedUseSavepoint() {
|
||||
transaction.setNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNestedUseSavepoint() {
|
||||
return transaction.isNestedUseSavepoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long profileOffset() {
|
||||
return transaction.profileOffset();
|
||||
@@ -235,6 +250,11 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
transaction.setBatchMode(useBatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchMode() {
|
||||
return transaction.isBatchMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch persistBatchMode) {
|
||||
transaction.setBatch(persistBatchMode);
|
||||
@@ -245,11 +265,21 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.getBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(boolean batchMode) {
|
||||
transaction.setBatchOnCascade(batchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
transaction.setBatchOnCascade(batchOnCascadeMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchOnCascade() {
|
||||
return transaction.isBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return transaction.getBatchOnCascade();
|
||||
@@ -346,8 +376,8 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest(PersistRequest.Type type) {
|
||||
return transaction.isBatchThisRequest(type);
|
||||
public boolean isBatchThisRequest() {
|
||||
return transaction.isBatchThisRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,6 +43,11 @@ public interface SpiUpdate<T> extends Update<T> {
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the label (for metrics collection).
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the type of this - insert, update or delete.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,9 @@ package io.ebeaninternal.api;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
import io.ebeaninternal.server.transaction.DeleteByIdMap;
|
||||
import io.ebeaninternal.server.transaction.TransactionManager;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -108,8 +110,20 @@ public class TransactionEvent implements Serializable {
|
||||
/**
|
||||
* Build and return the cache changeSet.
|
||||
*/
|
||||
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
|
||||
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
|
||||
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
|
||||
|
||||
if (eventBeans == null && deleteByIdMap == null && eventTables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis());
|
||||
if (eventTables != null && !eventTables.isEmpty()) {
|
||||
// notify cache with table based changes
|
||||
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
|
||||
for (TransactionEventTable.TableIUD tableIUD : eventTables.values()) {
|
||||
dm.cacheNotify(tableIUD, changeSet);
|
||||
}
|
||||
}
|
||||
if (eventBeans != null) {
|
||||
eventBeans.notifyCache(changeSet);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.event.BulkTableEvent;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessage;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessageList;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
@@ -12,7 +9,7 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TransactionEventTable implements Serializable {
|
||||
public final class TransactionEventTable implements Serializable, BinaryWritable {
|
||||
|
||||
private static final long serialVersionUID = 2236555729767483264L;
|
||||
|
||||
@@ -23,20 +20,13 @@ public final class TransactionEventTable implements Serializable {
|
||||
return "TransactionEventTable " + map.values();
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
@Override
|
||||
public void writeBinary(BinaryWriteContext out) throws IOException {
|
||||
for (TableIUD tableIud : map.values()) {
|
||||
tableIud.writeBinaryMessage(msgList);
|
||||
tableIud.writeBinary(out);
|
||||
}
|
||||
}
|
||||
|
||||
public void readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
|
||||
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
|
||||
map.put(tableIud.getTableName(), tableIud);
|
||||
}
|
||||
|
||||
|
||||
public void add(TransactionEventTable table) {
|
||||
|
||||
for (TableIUD iud : table.values()) {
|
||||
@@ -47,7 +37,6 @@ public final class TransactionEventTable implements Serializable {
|
||||
public void add(String table, boolean insert, boolean update, boolean delete) {
|
||||
|
||||
table = table.toUpperCase();
|
||||
|
||||
add(new TableIUD(table, insert, update, delete));
|
||||
}
|
||||
|
||||
@@ -67,7 +56,7 @@ public final class TransactionEventTable implements Serializable {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public static class TableIUD implements Serializable, BulkTableEvent {
|
||||
public static class TableIUD implements Serializable, BulkTableEvent, BinaryWritable {
|
||||
|
||||
private static final long serialVersionUID = -1958317571064162089L;
|
||||
|
||||
@@ -83,7 +72,7 @@ public final class TransactionEventTable implements Serializable {
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
public static TableIUD readBinaryMessage(BinaryReadContext dataInput) throws IOException {
|
||||
|
||||
String table = dataInput.readUTF();
|
||||
boolean insert = dataInput.readBoolean();
|
||||
@@ -93,17 +82,13 @@ public final class TransactionEventTable implements Serializable {
|
||||
return new TableIUD(table, insert, update, delete);
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
BinaryMessage msg = new BinaryMessage(table.length() + 10);
|
||||
DataOutputStream os = msg.getOs();
|
||||
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
|
||||
@Override
|
||||
public void writeBinary(BinaryWriteContext out) throws IOException {
|
||||
DataOutputStream os = out.start(TYPE_TABLEIUD);
|
||||
os.writeUTF(table);
|
||||
os.writeBoolean(insert);
|
||||
os.writeBoolean(update);
|
||||
os.writeBoolean(delete);
|
||||
|
||||
msgList.add(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
|
||||
String EVT_INSERT = "i";
|
||||
String EVT_UPDATE = "u";
|
||||
String EVT_DELETE = "d";
|
||||
String EVT_SOFT_DELETE = "ds";
|
||||
String EVT_DELETE_SOFT = "ds";
|
||||
String EVT_DELETE_PERMANENT = "dp";
|
||||
String EVT_ORMUPDATE = "uo";
|
||||
String FIND_UPDATE = "uq";
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ebeaninternal.api.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public interface SpiJsonReader {
|
||||
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
SpiJsonReader forJson(JsonParser moreJson, boolean resetContext);
|
||||
|
||||
<T> void persistenceContextPut(Object beanId, T currentBean);
|
||||
|
||||
Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc);
|
||||
|
||||
ObjectMapper getObjectMapper();
|
||||
|
||||
JsonParser getParser();
|
||||
|
||||
JsonToken nextToken() throws IOException;
|
||||
|
||||
void pushPath(String path);
|
||||
|
||||
void popPath();
|
||||
|
||||
void beanVisitor(Object bean, Map<String, Object> unmappedProperties);
|
||||
|
||||
Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;
|
||||
}
|
||||
+8
-2
@@ -1,9 +1,10 @@
|
||||
package io.ebeaninternal.server.text.json;
|
||||
package io.ebeaninternal.api.json;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.json.JsonWriter;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,11 @@ import java.util.Collection;
|
||||
*/
|
||||
public interface SpiJsonWriter extends JsonWriter {
|
||||
|
||||
/**
|
||||
* Flush the buffer.
|
||||
*/
|
||||
void flush() throws IOException;
|
||||
|
||||
/**
|
||||
* Return true if the value is a parent bean.
|
||||
*/
|
||||
@@ -60,7 +66,7 @@ public interface SpiJsonWriter extends JsonWriter {
|
||||
* Write value using underlying Jaskson object mapper if available.
|
||||
*/
|
||||
void writeValueUsingObjectMapper(String name, Object value);
|
||||
|
||||
|
||||
/**
|
||||
* Write the bean properties.
|
||||
*/
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.model.CurrentModel;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,6 +37,8 @@ public class DdlGenerator {
|
||||
private final boolean runDdl;
|
||||
private final boolean createOnly;
|
||||
private final boolean jaxbPresent;
|
||||
private final boolean ddlCommitOnCreateIndex;
|
||||
private final String dbSchema;
|
||||
|
||||
private CurrentModel currentModel;
|
||||
private String dropAllContent;
|
||||
@@ -47,11 +49,14 @@ public class DdlGenerator {
|
||||
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
|
||||
this.generateDdl = serverConfig.isDdlGenerate();
|
||||
this.createOnly = serverConfig.isDdlCreateOnly();
|
||||
this.dbSchema = serverConfig.getDbSchema();
|
||||
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
|
||||
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
|
||||
this.runDdl = false;
|
||||
this.ddlCommitOnCreateIndex = false;
|
||||
} else {
|
||||
this.runDdl = serverConfig.isDdlRun();
|
||||
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,96 +87,122 @@ public class DdlGenerator {
|
||||
* Run the DDL drop and DDL create scripts if properties have been set.
|
||||
*/
|
||||
protected void runDdl() {
|
||||
|
||||
if (runDdl) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
runInitSql();
|
||||
runDropSql();
|
||||
runCreateSql();
|
||||
runSeedSql();
|
||||
|
||||
} catch (IOException e) {
|
||||
String msg = "Error reading drop/create script from file system";
|
||||
throw new RuntimeException(msg, e);
|
||||
connection = obtainConnection();
|
||||
runDdlWith(connection);
|
||||
} finally {
|
||||
JdbcClose.rollback(connection);
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void runDdlWith(Connection connection) {
|
||||
try {
|
||||
if (dbSchema != null) {
|
||||
createSchemaIfRequired(connection);
|
||||
}
|
||||
runInitSql(connection);
|
||||
runDropSql(connection);
|
||||
runCreateSql(connection);
|
||||
runSeedSql(connection);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error reading drop/create script from file system", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Connection obtainConnection() {
|
||||
try {
|
||||
return server.getPluginApi().getDataSource().getConnection();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to obtain connection to run DDL", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void createSchemaIfRequired(Connection connection) {
|
||||
try {
|
||||
server.getDatabasePlatform().createSchemaIfNotExists(dbSchema, connection);
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to create DB Schema", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the DDL statements in the script.
|
||||
*/
|
||||
public int runScript(boolean expectErrors, String content, String scriptName) {
|
||||
public int runScript(Connection connection, boolean expectErrors, String content, String scriptName) {
|
||||
|
||||
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
|
||||
|
||||
Transaction transaction = server.createTransaction();
|
||||
Connection connection = transaction.getConnection();
|
||||
try {
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(true);
|
||||
} else if (ddlCommitOnCreateIndex) {
|
||||
runner.setCommitOnCreateIndex();
|
||||
}
|
||||
int count = runner.runAll(content, connection);
|
||||
if (expectErrors) {
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
transaction.commit();
|
||||
connection.commit();
|
||||
return count;
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Failed to run script", e);
|
||||
|
||||
} finally {
|
||||
transaction.end();
|
||||
JdbcClose.rollback(connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runDropSql() throws IOException {
|
||||
protected void runDropSql(Connection connection) throws IOException {
|
||||
if (!createOnly) {
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-dll");
|
||||
}
|
||||
}
|
||||
|
||||
if (dropAllContent == null) {
|
||||
dropAllContent = readFile(getDropFileName());
|
||||
}
|
||||
runScript(true, dropAllContent, getDropFileName());
|
||||
runScript(connection, true, dropAllContent, getDropFileName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void runCreateSql() throws IOException {
|
||||
protected void runCreateSql(Connection connection) throws IOException {
|
||||
if (createAllContent == null) {
|
||||
createAllContent = readFile(getCreateFileName());
|
||||
}
|
||||
runScript(false, createAllContent, getCreateFileName());
|
||||
runScript(connection, false, createAllContent, getCreateFileName());
|
||||
|
||||
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
|
||||
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
|
||||
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
|
||||
if (extraApply != null) {
|
||||
runScript(false, extraApply, "extra-dll");
|
||||
runScript(connection, false, extraApply, "extra-dll");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void runInitSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlInitSql());
|
||||
protected void runInitSql(Connection connection) throws IOException {
|
||||
runResourceScript(connection, server.getServerConfig().getDdlInitSql());
|
||||
}
|
||||
|
||||
protected void runSeedSql() throws IOException {
|
||||
runResourceScript(server.getServerConfig().getDdlSeedSql());
|
||||
protected void runSeedSql(Connection connection) throws IOException {
|
||||
runResourceScript(connection, server.getServerConfig().getDdlSeedSql());
|
||||
}
|
||||
|
||||
protected void runResourceScript(String sqlScript) throws IOException {
|
||||
protected void runResourceScript(Connection connection, String sqlScript) throws IOException {
|
||||
|
||||
if (sqlScript != null) {
|
||||
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(false, content, sqlScript);
|
||||
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
|
||||
if (is != null) {
|
||||
String content = readContent(new InputStreamReader(is));
|
||||
runScript(connection, false, content, sqlScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.EbeanServer;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
import io.ebean.config.PlatformConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
@@ -15,7 +16,8 @@ import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
|
||||
import io.ebean.dbmigration.DbMigration;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -89,6 +91,12 @@ public class DefaultDbMigration implements DbMigration {
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
protected Boolean strictMode;
|
||||
protected Boolean includeGeneratedFileComment;
|
||||
protected String header;
|
||||
protected String applyPrefix;
|
||||
protected String version;
|
||||
protected String name;
|
||||
protected String generatePendingDrop;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
@@ -146,6 +154,36 @@ public class DefaultDbMigration implements DbMigration {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplyPrefix(String applyPrefix) {
|
||||
this.applyPrefix = applyPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratePendingDrop(String generatePendingDrop) {
|
||||
this.generatePendingDrop = generatePendingDrop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
|
||||
this.includeGeneratedFileComment = includeGeneratedFileComment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
@@ -226,15 +264,17 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (!online) {
|
||||
DbOffline.setGenerateMigration();
|
||||
if (databasePlatform == null && !platforms.isEmpty()) {
|
||||
// for multiple platform generation set the general platform
|
||||
// to H2 so that it runs offline without DB connection
|
||||
// for multiple platform generation the first platform
|
||||
// is used to generate the "logical" model diff
|
||||
setPlatform(platforms.get(0).platform);
|
||||
}
|
||||
}
|
||||
setDefaults();
|
||||
if (!platforms.isEmpty()) {
|
||||
configurePlatforms();
|
||||
}
|
||||
try {
|
||||
Request request = createRequest();
|
||||
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform);
|
||||
}
|
||||
@@ -253,6 +293,16 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration for each of the target platforms.
|
||||
*/
|
||||
private void configurePlatforms() {
|
||||
for (Pair pair : platforms) {
|
||||
PlatformConfig config = serverConfig.newPlatformConfig("dbmigration.platform", pair.prefix);
|
||||
pair.platform.configure(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate "repeatable" migration scripts.
|
||||
* <p>
|
||||
@@ -264,24 +314,28 @@ public class DefaultDbMigration implements DbMigration {
|
||||
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
|
||||
|
||||
if (dbPlatform != null) {
|
||||
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
|
||||
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
|
||||
}
|
||||
}
|
||||
|
||||
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
|
||||
if (extraDdl != null) {
|
||||
List<DdlScript> ddlScript = extraDdl.getDdlScript();
|
||||
for (DdlScript script : ddlScript) {
|
||||
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
|
||||
writeExtraDdl(migrationDir, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write (or override) the "repeatable" migration script.
|
||||
*/
|
||||
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
|
||||
|
||||
String fullName = repeatableMigrationName(script.getName());
|
||||
String fullName = repeatableMigrationName(script.isInit(), script.getName());
|
||||
|
||||
logger.info("writing repeatable script {}", fullName);
|
||||
|
||||
@@ -292,8 +346,18 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
private String repeatableMigrationName(String scriptName) {
|
||||
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
|
||||
|
||||
|
||||
private String repeatableMigrationName(boolean init, String scriptName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (init) {
|
||||
sb.append("I__");
|
||||
} else {
|
||||
sb.append("R__");
|
||||
}
|
||||
sb.append(scriptName.replace(' ', '_'));
|
||||
sb.append(migrationConfig.getApplySuffix());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,8 +562,28 @@ public class DefaultDbMigration implements DbMigration {
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
if (strictMode != null && migrationConfig != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
if (migrationConfig != null) {
|
||||
if (strictMode != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
}
|
||||
if (applyPrefix != null) {
|
||||
migrationConfig.setApplyPrefix(applyPrefix);
|
||||
}
|
||||
if (header != null) {
|
||||
migrationConfig.setDdlHeader(header);
|
||||
}
|
||||
if (includeGeneratedFileComment != null) {
|
||||
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
|
||||
}
|
||||
if (version != null) {
|
||||
migrationConfig.setVersion(version);
|
||||
}
|
||||
if (name != null) {
|
||||
migrationConfig.setName(name);
|
||||
}
|
||||
if (generatePendingDrop != null) {
|
||||
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,8 +638,12 @@ public class DefaultDbMigration implements DbMigration {
|
||||
return new OraclePlatform();
|
||||
case SQLANYWHERE:
|
||||
return new SqlAnywherePlatform();
|
||||
case SQLSERVER16:
|
||||
return new SqlServer16Platform();
|
||||
case SQLSERVER17:
|
||||
return new SqlServer17Platform();
|
||||
case SQLSERVER:
|
||||
return new SqlServerPlatform();
|
||||
throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details");
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
|
||||
@@ -18,7 +18,9 @@ public class DdlWrite {
|
||||
|
||||
private final DdlBuffer applyForeignKeys;
|
||||
|
||||
private final DdlBuffer applyHistory;
|
||||
private final DdlBuffer applyHistoryView;
|
||||
|
||||
private final DdlBuffer applyHistoryTrigger;
|
||||
|
||||
private final DdlBuffer dropAllForeignKeys;
|
||||
|
||||
@@ -39,7 +41,8 @@ public class DdlWrite {
|
||||
this.applyDropDependencies = new BaseDdlBuffer(configuration);
|
||||
this.apply = new BaseDdlBuffer(configuration);
|
||||
this.applyForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.applyHistory = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryView = new BaseDdlBuffer(configuration);
|
||||
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
|
||||
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
|
||||
this.dropAll = new BaseDdlBuffer(configuration);
|
||||
}
|
||||
@@ -61,7 +64,8 @@ public class DdlWrite {
|
||||
public boolean isApplyEmpty() {
|
||||
return apply.getBuffer().isEmpty()
|
||||
&& applyForeignKeys.getBuffer().isEmpty()
|
||||
&& applyHistory.getBuffer().isEmpty()
|
||||
&& applyHistoryView.getBuffer().isEmpty()
|
||||
&& applyHistoryTrigger.getBuffer().isEmpty()
|
||||
&& applyDropDependencies.getBuffer().isEmpty();
|
||||
}
|
||||
|
||||
@@ -90,10 +94,17 @@ public class DdlWrite {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history DDL is written to.
|
||||
* Return the buffer that apply history-view DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistory() {
|
||||
return applyHistory;
|
||||
public DdlBuffer applyHistoryView() {
|
||||
return applyHistoryView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the buffer that apply history-trigger DDL is written to.
|
||||
*/
|
||||
public DdlBuffer applyHistoryTrigger() {
|
||||
return applyHistoryTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+56
-25
@@ -42,6 +42,12 @@ import java.util.Map;
|
||||
*/
|
||||
public class BaseTableDdl implements TableDdl {
|
||||
|
||||
enum HistorySupport {
|
||||
NONE,
|
||||
SQL2011,
|
||||
TRIGGER_BASED
|
||||
}
|
||||
|
||||
protected final DbConstraintNaming naming;
|
||||
|
||||
protected final NamingConvention namingConvention;
|
||||
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
private boolean strictMode;
|
||||
|
||||
private final boolean sql2011History;
|
||||
private final HistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* Helper class that is used to execute the migration ddl before and after the migration action.
|
||||
*/
|
||||
private class DdlMigrationHelp {
|
||||
private List<String> before;
|
||||
private List<String> after;
|
||||
private String tableName;
|
||||
private String columnName;
|
||||
private String defaultValue;
|
||||
private final List<String> before;
|
||||
private final List<String> after;
|
||||
private final String tableName;
|
||||
private final String columnName;
|
||||
private final String defaultValue;
|
||||
private final boolean withHistory;
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when adding a NEW column.
|
||||
*/
|
||||
DdlMigrationHelp(String tableName, Column column) {
|
||||
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
|
||||
this.tableName = tableName;
|
||||
this.columnName = column.getName();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
|
||||
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
|
||||
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
|
||||
this.withHistory = withHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
|
||||
// here we add the platform's default update script
|
||||
withHistory = isTrue(alter.isWithHistory());
|
||||
if (alter.getBefore().isEmpty() && alterNotNull) {
|
||||
if (defaultValue == null) {
|
||||
handleStrictError(tableName, columnName);
|
||||
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (!before.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
|
||||
if (!before.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
for (String ddlScript : before) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
|
||||
buffer.endOfStatement();
|
||||
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
|
||||
void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
if (!after.isEmpty() && withHistory) {
|
||||
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
|
||||
}
|
||||
// here we run post migration scripts
|
||||
for (String ddlScript : after) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
|
||||
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
|
||||
this.platformDdl.configure(serverConfig);
|
||||
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
|
||||
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
|
||||
this.sql2011History = hist != null && hist.isStandardsBased();
|
||||
if (hist == null) {
|
||||
this.historySupport = HistorySupport.NONE;
|
||||
} else {
|
||||
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,15 +455,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, WriteForeignKey request) throws IOException {
|
||||
|
||||
String tableName = lowerTableName(request.table());
|
||||
DdlBuffer fkeyBuffer = write.applyForeignKeys();
|
||||
alterTableAddForeignKey(fkeyBuffer, request);
|
||||
|
||||
String tableName = lowerTableName(request.table());
|
||||
if (request.indexName() != null) {
|
||||
// no matching unique constraint so add the index
|
||||
fkeyBuffer.append(platformDdl.createIndex(request.indexName(), tableName, request.cols())).endOfStatement();
|
||||
}
|
||||
|
||||
alterTableAddForeignKey(fkeyBuffer, request);
|
||||
|
||||
fkeyBuffer.end();
|
||||
|
||||
write.dropAllForeignKeys()
|
||||
@@ -455,7 +475,6 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
|
||||
write.dropAllForeignKeys().end();
|
||||
|
||||
}
|
||||
|
||||
protected void alterTableAddForeignKey(DdlBuffer buffer, WriteForeignKey request) throws IOException {
|
||||
@@ -701,8 +720,14 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
@Override
|
||||
public void generateEpilog(DdlWrite write) throws IOException {
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
if (!regenerateHistoryTriggers.isEmpty()) {
|
||||
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
|
||||
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
|
||||
platformDdl.regenerateHistoryTriggers(write, update);
|
||||
}
|
||||
|
||||
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
|
||||
}
|
||||
platformDdl.generateEpilog(write);
|
||||
}
|
||||
@@ -723,15 +748,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = addColumn.getTableName();
|
||||
List<Column> columns = addColumn.getColumn();
|
||||
for (Column column : columns) {
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false);
|
||||
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
|
||||
}
|
||||
|
||||
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// make same changes to the history table
|
||||
String historyTable = historyTable(tableName);
|
||||
for (Column column : columns) {
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true);
|
||||
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +797,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String tableName = dropColumn.getTableName();
|
||||
|
||||
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
|
||||
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
|
||||
|
||||
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
// also drop from the history table
|
||||
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
|
||||
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
|
||||
@@ -814,12 +840,15 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(alterColumn.getComment())) {
|
||||
alterColumnComment(writer, alterColumn);
|
||||
}
|
||||
if (hasValue(alterColumn.getDropCheckConstraint())) {
|
||||
dropCheckConstraint(writer, alterColumn, alterColumn.getDropCheckConstraint());
|
||||
}
|
||||
|
||||
boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint());
|
||||
|
||||
if (alterCheckConstraint) {
|
||||
// drop constraint before altering type etc
|
||||
dropCheckConstraint(writer, alterColumn);
|
||||
dropCheckConstraint(writer, alterColumn, alterColumn.getCheckConstraintName());
|
||||
}
|
||||
boolean alterBaseAttributes = false;
|
||||
if (hasValue(alterColumn.getType())) {
|
||||
@@ -873,8 +902,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
|
||||
// mysql and sql server column type change allowing nulls in the history table column
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
AlterColumn alterHistoryColumn = new AlterColumn();
|
||||
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
|
||||
alterHistoryColumn.setColumnName(alter.getColumnName());
|
||||
@@ -895,9 +925,9 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
}
|
||||
|
||||
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
|
||||
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
|
||||
|
||||
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), alter.getCheckConstraintName());
|
||||
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName);
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
}
|
||||
@@ -924,7 +954,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
|
||||
if (hasValue(ddl)) {
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
if (isTrue(alter.isWithHistory()) && !sql2011History) {
|
||||
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
|
||||
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
|
||||
// apply same type change to matching column in the history table
|
||||
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
|
||||
writer.apply().append(ddl).endOfStatement();
|
||||
@@ -980,8 +1011,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
|
||||
}
|
||||
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
|
||||
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
|
||||
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
|
||||
if (!onHistoryTable) {
|
||||
help.writeBefore(buffer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
/**
|
||||
* CockroachDB specific DDL handling.
|
||||
*/
|
||||
public class CockroachDdl extends PlatformDdl {
|
||||
|
||||
public CockroachDdl(DatabasePlatform platform) {
|
||||
super(platform);
|
||||
this.historyDdl = new PostgresHistoryDdl();
|
||||
this.dropTableCascade = " cascade";
|
||||
this.columnSetType = "type ";
|
||||
this.alterTableIfExists = "if exists ";
|
||||
this.columnSetNull = "drop not null";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertArrayType(String logicalArrayType) {
|
||||
return NativeDbArray.logicalToNative(logicalArrayType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map bigint, integer and smallint all into serial.
|
||||
*/
|
||||
@Override
|
||||
public String asIdentityColumn(String columnDefn) {
|
||||
|
||||
if ("bigint".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("integer".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("smallint".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInlineComments() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
*/
|
||||
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
|
||||
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
|
||||
|
||||
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
|
||||
}
|
||||
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
DdlBuffer apply = writer.applyHistoryView();
|
||||
|
||||
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
|
||||
createHistoryTable(apply, table);
|
||||
@@ -210,19 +210,19 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
.endOfStatement().end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or replace the with_history view with explicit columns.
|
||||
*/
|
||||
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer apply = update.historyBuffer();
|
||||
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
|
||||
appendColumnNames(apply, update.getColumns(), "");
|
||||
appendSysPeriodColumns(apply, ", ");
|
||||
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
|
||||
appendColumnNames(apply, update.getColumns(), "");
|
||||
appendSysPeriodColumns(apply, ", ");
|
||||
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
|
||||
/**
|
||||
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
|
||||
* but otherwise we need to drop and create it.
|
||||
*/
|
||||
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
|
||||
|
||||
DdlBuffer buffer = update.dropDependencyBuffer();
|
||||
// we need to drop the view early/first before any changes to the tables etc
|
||||
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
|
||||
|
||||
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
|
||||
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
|
||||
}
|
||||
|
||||
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
|
||||
|
||||
+10
-2
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyBuffer() {
|
||||
return writer.applyHistory();
|
||||
public DdlBuffer historyViewBuffer() {
|
||||
return writer.applyHistoryView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the appropriate buffer for the current mode.
|
||||
*/
|
||||
public DdlBuffer historyTriggerBuffer() {
|
||||
return writer.applyHistoryTrigger();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the appropriate drop dependency buffer for the current mode.
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user