mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
264
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bd9e67703 | ||
|
|
2a986ab590 | ||
|
|
7c477827fe | ||
|
|
b2967dec1e | ||
|
|
a95fd063ae | ||
|
|
17a54e3804 | ||
|
|
f1b2a09b30 | ||
|
|
e4d9e4f2de | ||
|
|
6474f306b5 | ||
|
|
f1c2f134ad | ||
|
|
0f7158ea18 | ||
|
|
3fdb76d18f | ||
|
|
b1027a2f25 | ||
|
|
e5a1f9f7cc | ||
|
|
f78f24136a | ||
|
|
5b2aa0c74d | ||
|
|
175714046a | ||
|
|
73b2cc55f3 | ||
|
|
cebe375856 | ||
|
|
2f47235018 | ||
|
|
17abd6d977 | ||
|
|
cc5f3689f3 | ||
|
|
abcc651e7f | ||
|
|
caa36262c1 | ||
|
|
66b6dba921 | ||
|
|
fc01f230f3 | ||
|
|
7fe3e657d7 | ||
|
|
9bade12c9d | ||
|
|
7a816ae2da | ||
|
|
b83412281e | ||
|
|
478bba292d | ||
|
|
77a4e684bb | ||
|
|
a8254c4295 | ||
|
|
2a5d0084ef | ||
|
|
f9404639d7 | ||
|
|
bbef5a6bbb | ||
|
|
4645fd9725 | ||
|
|
823cb45221 | ||
|
|
b2bbb05b83 | ||
|
|
5ff71324d0 | ||
|
|
851f1c98b0 | ||
|
|
a92e620f7e | ||
|
|
f9cd955f7a | ||
|
|
71f66005e7 | ||
|
|
b682293a32 | ||
|
|
5bd7853ce8 | ||
|
|
276c56161a | ||
|
|
6b01821623 | ||
|
|
8495ccced6 | ||
|
|
2d929837c4 | ||
|
|
6754992b1c | ||
|
|
08b7f4a95f | ||
|
|
07e2605a30 | ||
|
|
9a0b742f14 | ||
|
|
bce745e88d | ||
|
|
51d2e4c600 | ||
|
|
4e33027fdf | ||
|
|
0a809b0fb3 | ||
|
|
13ddb915c8 | ||
|
|
07cdf64ece | ||
|
|
b9ae76d465 | ||
|
|
82f5c21d26 | ||
|
|
14105eb1eb | ||
|
|
bab27f25ca | ||
|
|
eed8d69715 | ||
|
|
7b93df2df7 | ||
|
|
bb205ced54 | ||
|
|
0d4c50e19c | ||
|
|
8cff85fdda | ||
|
|
932986a6b5 | ||
|
|
01539091ef | ||
|
|
7e69f71b51 | ||
|
|
58d6b2c24e | ||
|
|
3c7ef7d46e | ||
|
|
db5db26fca | ||
|
|
7110f4a59b | ||
|
|
9f91d8bcf9 | ||
|
|
ac97444ad1 | ||
|
|
f6e9afcab6 | ||
|
|
a7226079fe | ||
|
|
fc915010d9 | ||
|
|
c1818bc309 | ||
|
|
4a4134ee93 | ||
|
|
e07bd8f736 | ||
|
|
4d0b29624e | ||
|
|
c7f4b56dd1 | ||
|
|
69c045443c | ||
|
|
fd2d083971 | ||
|
|
b9380aa5b8 | ||
|
|
3a1368a342 | ||
|
|
0e9fc31531 | ||
|
|
5132107748 | ||
|
|
7ff3d703f7 | ||
|
|
ef91c643ed | ||
|
|
f0ddf6c7e6 | ||
|
|
fcff571664 | ||
|
|
d5555ed9a9 | ||
|
|
c5a11aa5a8 | ||
|
|
d1099c8309 | ||
|
|
7ff8758797 | ||
|
|
f1ebe7929b | ||
|
|
0056568bc7 | ||
|
|
4b72924529 | ||
|
|
a75ed676f8 | ||
|
|
c3438b888d | ||
|
|
56be274acc | ||
|
|
84bb4ff78c | ||
|
|
4054eda1f6 | ||
|
|
a4b9354e43 | ||
|
|
06e033f23f | ||
|
|
156f505394 | ||
|
|
9b2b2f7787 | ||
|
|
e68dae0baa | ||
|
|
2cf864954f | ||
|
|
2b94487573 | ||
|
|
fcf730ec39 | ||
|
|
7d343455ac | ||
|
|
21e0b748a6 | ||
|
|
2fab733e4e | ||
|
|
7260a1b6eb | ||
|
|
434a005366 | ||
|
|
ff67ef60b0 | ||
|
|
0a1747d8e3 | ||
|
|
f36499f71f | ||
|
|
da818f17c2 | ||
|
|
10eb2bc21f | ||
|
|
66fe278e3f | ||
|
|
10607385e0 | ||
|
|
db6562df76 | ||
|
|
c0ec87d4cd | ||
|
|
0dbba7a211 | ||
|
|
e75670f207 | ||
|
|
fb8db14f3a | ||
|
|
c00b1849ef | ||
|
|
1e1c518641 | ||
|
|
d5b42f9ba1 | ||
|
|
bda564d2c8 | ||
|
|
dce124cabd | ||
|
|
f5cfcbd56e | ||
|
|
5bccdccbd6 | ||
|
|
6e62b72fb9 | ||
|
|
89dac3820b | ||
|
|
ea836a0078 | ||
|
|
1ffd7c7720 | ||
|
|
387582543c | ||
|
|
a215878a7c | ||
|
|
dbedb65922 | ||
|
|
1d52b3a818 | ||
|
|
6ce2b91fff | ||
|
|
55d66ae851 | ||
|
|
37d001f754 | ||
|
|
c121618c0c | ||
|
|
7ebef51859 | ||
|
|
093f82a87f | ||
|
|
b60f1f978f | ||
|
|
6ac146cabe | ||
|
|
2263d31f46 | ||
|
|
4a66d0be6b | ||
|
|
e68e496207 | ||
|
|
088110385f | ||
|
|
a355f66561 | ||
|
|
00fdb632d7 | ||
|
|
a54c4157e6 | ||
|
|
a89a9f0202 | ||
|
|
331f49824f | ||
|
|
94a857d345 | ||
|
|
1005f177fd | ||
|
|
96a4b93c75 | ||
|
|
5869187368 | ||
|
|
eac50b8f5b | ||
|
|
6e2712ea49 | ||
|
|
f6e5446dab | ||
|
|
4879d44da4 | ||
|
|
2c5bf98d0b | ||
|
|
45a571bd97 | ||
|
|
d0ca75c25b | ||
|
|
fb1c7667cc | ||
|
|
1909ba1557 | ||
|
|
e4eca4bbe9 | ||
|
|
b2438ecd71 | ||
|
|
ed08567d06 | ||
|
|
bbe8abcc2e | ||
|
|
1651d5e119 | ||
|
|
64bf6d6a35 | ||
|
|
47b0a917a2 | ||
|
|
2fe1a349c3 | ||
|
|
f168a8fd0d | ||
|
|
dda98c1dda | ||
|
|
88a13c5416 | ||
|
|
16b8f5da35 | ||
|
|
3471f6d206 | ||
|
|
0bf4c84bc8 | ||
|
|
d366336ee5 | ||
|
|
bf3f8ecff5 | ||
|
|
7524903aea | ||
|
|
90cf3fddc9 | ||
|
|
8e0f9f7591 | ||
|
|
24936c0a41 | ||
|
|
f02a552cdf | ||
|
|
5a5cb7ffbd | ||
|
|
f85fa772bf | ||
|
|
9f9ec8ef9f | ||
|
|
62707caf64 | ||
|
|
c6fc7a032d | ||
|
|
10faa1fae6 | ||
|
|
764f5ce4d4 | ||
|
|
65ba231fc0 | ||
|
|
1da2382e45 | ||
|
|
a86af94a8c | ||
|
|
8301cb634e | ||
|
|
721654d033 | ||
|
|
fe1817ef02 | ||
|
|
fc996659f2 | ||
|
|
368d22baa6 | ||
|
|
daf3d97266 | ||
|
|
f2179fca2d | ||
|
|
2c436c3af5 | ||
|
|
8d334ff2e8 | ||
|
|
f3e4696498 | ||
|
|
221260fa44 | ||
|
|
f8e7a79e9a | ||
|
|
b66f18bfd6 | ||
|
|
ac6708e0d0 | ||
|
|
a5f46c095d | ||
|
|
3ac7ea6e60 | ||
|
|
b2e0271ce7 | ||
|
|
c7a7651602 | ||
|
|
1e6678cbb4 | ||
|
|
a6e4e181bf | ||
|
|
b8e1dffca2 | ||
|
|
7e8ed72b7b | ||
|
|
ec8dd30a04 | ||
|
|
f81d5c9f3a | ||
|
|
74b24b1db1 | ||
|
|
179ef8c8a0 | ||
|
|
f77f3c3d6e | ||
|
|
9a7646195c | ||
|
|
84d3578e33 | ||
|
|
ef05b81014 | ||
|
|
726c682984 | ||
|
|
0c22a99fdf | ||
|
|
c062c8e001 | ||
|
|
06f3c000f5 | ||
|
|
8c0232fdb8 | ||
|
|
2470486f20 | ||
|
|
8fbf0374dd | ||
|
|
dd8fff0277 | ||
|
|
73b3d1d977 | ||
|
|
2a76ae2192 | ||
|
|
39931a3a2c | ||
|
|
58eb49617b | ||
|
|
9b621d2ec7 | ||
|
|
f2e8b652fa | ||
|
|
f342d9d561 | ||
|
|
8009bdf099 | ||
|
|
7336ce81bd | ||
|
|
f805bec8f2 | ||
|
|
46cdc84e6b | ||
|
|
e908d16ba8 | ||
|
|
96e3f4138f | ||
|
|
977dd52890 | ||
|
|
a9b7bb2175 | ||
|
|
f83c7fdac8 | ||
|
|
a695fb59b2 |
+1
-1
@@ -13,7 +13,7 @@ ebean-profiling*.xml
|
||||
/db
|
||||
/mydb.db
|
||||
!src/test/ddl-review/*.sql
|
||||
|
||||
profiling/
|
||||
|
||||
# Intellij project files
|
||||
*.iml
|
||||
|
||||
+11
@@ -9,16 +9,27 @@ git:
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
# apt:
|
||||
# packages:
|
||||
# - mysql-server-5.7
|
||||
# - mysql-client-core-5.7
|
||||
# - mysql-client-5.7
|
||||
|
||||
services:
|
||||
- docker
|
||||
- postgresql
|
||||
- mysql
|
||||
|
||||
before_script:
|
||||
- ./.travis/setup_database
|
||||
- mysql -u root -e 'CREATE DATABASE test_ebean;'
|
||||
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
|
||||
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
|
||||
|
||||
env:
|
||||
- EBEAN_DB=h2
|
||||
- EBEAN_DB=pg
|
||||
# - EBEAN_DB=mysql
|
||||
|
||||
install: true
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>10.4.3</version>
|
||||
<version>11.10.4</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-10.4.3</tag>
|
||||
<tag>ebean-11.10.4</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -72,10 +72,26 @@
|
||||
</profiles>
|
||||
<dependencies>
|
||||
|
||||
<!--
|
||||
Projects are expected to explicit depend on version
|
||||
of slf4j that they want to use
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>13.0</version>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Class retention Nonnull and Nullable annotations
|
||||
to assist with IDE auto-completion with Ebean API
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jsr305</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -87,43 +103,25 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource-api</artifactId>
|
||||
<version>1.1</version>
|
||||
<version>3.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-datasource</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner-api</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner</artifactId>
|
||||
<version>2.2.2</version>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-dbmigration</artifactId>
|
||||
<version>10.1.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>[1.7.1,1.7.99)</version>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -200,6 +198,20 @@
|
||||
<!--<scope>test</scope>-->
|
||||
<!--</dependency>-->
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-docker-run</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-agentloader</artifactId>
|
||||
@@ -210,7 +222,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>10.4.1</version>
|
||||
<version>11.9.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -219,7 +231,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.193</version>
|
||||
<version>1.4.196</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -283,7 +295,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>10.4.1</version>
|
||||
<version>11.9.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
@@ -340,7 +352,7 @@
|
||||
<overview>src/main/java/io/ebean/overview.html</overview>
|
||||
<source>1.8</source>
|
||||
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
|
||||
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util:io.ebean.dbmigration.ddlgeneration:io.ebean.dbmigration.migration:io.ebean.dbmigration.migrationreader:io.ebean.dbmigration.model</excludePackageNames>
|
||||
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
|
||||
<docletArtifact>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>pygments-doclet</artifactId>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -99,16 +98,6 @@ public interface BeanState {
|
||||
*/
|
||||
void setReadOnly(boolean readOnly);
|
||||
|
||||
/**
|
||||
* Add a propertyChangeListener.
|
||||
*/
|
||||
void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Remove a propertyChangeListener.
|
||||
*/
|
||||
void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Advanced - Used to programmatically build a partially or fully loaded
|
||||
* entity bean. First create an entity bean via
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Enum to control the different cache modes for queryCache and beanCache.
|
||||
* <h3>Bean cache</h3>
|
||||
* <p>
|
||||
* The bean cache is automatically used by default on <code>@Cache</code> beans for
|
||||
* the following queries:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>findOne() by id</li>
|
||||
* <li>findOne() by natural key(s)</li>
|
||||
* <li>findList() by ids</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
|
||||
* </p>
|
||||
* <h3>Query cache</h3>
|
||||
* <p>
|
||||
* For query cache use note that you must be careful, what you do with the returned collection.
|
||||
* By default the returned collections are read only and you will get an exception if you try
|
||||
* to change them.
|
||||
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
|
||||
* one in the cache. That means, changing does not affect the cache.
|
||||
* </p>
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public enum CacheMode {
|
||||
|
||||
/**
|
||||
* Do not use cache.
|
||||
*/
|
||||
OFF(false, false),
|
||||
|
||||
/**
|
||||
* Use the cache and store a result when needed.
|
||||
*/
|
||||
ON(true, true),
|
||||
|
||||
/**
|
||||
* Only used for bean caching.
|
||||
* <p>
|
||||
* The bean cache is automatically used by default on <code>@Cache</code> beans for
|
||||
* the following queries:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>findOne() by id</li>
|
||||
* <li>findOne() by natural key(s)</li>
|
||||
* <li>findList() by ids</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
|
||||
* </p>
|
||||
*/
|
||||
AUTO(true, true),
|
||||
|
||||
/**
|
||||
* Do not read from cache, but put beans into the cache and invalidate parts of the cache as necessary.
|
||||
* <p>
|
||||
* Use this on a query if you want to get the fresh value from database and put it into the cache.
|
||||
*/
|
||||
PUT(false, true),
|
||||
|
||||
/**
|
||||
* GET only from the cache.
|
||||
* <p>
|
||||
* This mode does not put entries into the cache or invalidate parts of the cache.
|
||||
*/
|
||||
GET(true, false);
|
||||
|
||||
private boolean get;
|
||||
private boolean put;
|
||||
|
||||
CacheMode(boolean get, boolean put) {
|
||||
this.get = get;
|
||||
this.put = put;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if value is read from cache.
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return get;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a newly loaded value (from database) is put into the cache.
|
||||
*/
|
||||
public boolean isPut() {
|
||||
return put;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package io.ebean;
|
||||
|
||||
import io.ebeanservice.docstore.api.DocQueryRequest;
|
||||
import io.ebeanservice.docstore.api.RawDoc;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -56,7 +56,7 @@ public interface DocumentStore {
|
||||
* Return the bean by fetching it's content from the document store.
|
||||
* If the document is not found null is returned.
|
||||
* <p>
|
||||
* Typically this is called indirectly by findUnique() on the query.
|
||||
* Typically this is called indirectly by findOne() on the query.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -64,7 +64,7 @@ public interface DocumentStore {
|
||||
* server.find(Customer.class)
|
||||
* .setUseDocStore(true)
|
||||
* .setId(42)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@@ -123,11 +123,8 @@ public interface DocumentStore {
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEach(new QueryEachConsumer<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
* }
|
||||
* .findEach((Order order) -> {
|
||||
* // process the bean ...
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
@@ -151,7 +148,7 @@ public interface DocumentStore {
|
||||
* server.find(Order.class)
|
||||
* .setUseDocStore(true)
|
||||
* .where()... // perhaps add predicates
|
||||
* .findEachWhile(new QueryEachWhileConsumer<Order>() {
|
||||
* .findEachWhile(new Predicate<Order>() {
|
||||
* @Override
|
||||
* public void accept(Order bean) {
|
||||
* // process the bean
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -129,8 +131,7 @@ public final class Ebean {
|
||||
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor
|
||||
* object.
|
||||
* Cache for synchronized read, creation and put. Protected by the monitor object.
|
||||
*/
|
||||
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
|
||||
|
||||
@@ -144,14 +145,7 @@ public final class Ebean {
|
||||
private ServerManager() {
|
||||
|
||||
try {
|
||||
// skipDefaultServer is set by EbeanServerFactory
|
||||
// ... when it is creating the primaryServer
|
||||
if (PrimaryServer.isSkip()) {
|
||||
// primary server being created by EbeanServerFactory
|
||||
// ... so we should not try and create it here
|
||||
logger.debug("PrimaryServer.isSkip()");
|
||||
|
||||
} else {
|
||||
if (!PrimaryServer.isSkip()) {
|
||||
// look to see if there is a default server defined
|
||||
String defaultName = PrimaryServer.getDefaultServerName();
|
||||
logger.debug("defaultName:{}", defaultName);
|
||||
@@ -289,7 +283,7 @@ public final class Ebean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
* Backdoor for registering a mock implementation of EbeanServer as the default server.
|
||||
*/
|
||||
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
|
||||
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
|
||||
@@ -371,9 +365,10 @@ public final class Ebean {
|
||||
/**
|
||||
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
|
||||
* <p>
|
||||
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
|
||||
* {@link #execute(TxScope, TxRunnable)}.
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
*
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
* <pre>{@code
|
||||
* // Start a new transaction. If there is a current transaction
|
||||
@@ -822,7 +817,7 @@ public final class Ebean {
|
||||
* }</pre>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -843,7 +838,8 @@ public final class Ebean {
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* Customer customer = order.getCustomer();
|
||||
* Address shippingAddress = customer.getShippingAddress();
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -959,7 +955,7 @@ public final class Ebean {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -991,7 +987,7 @@ public final class Ebean {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, eql);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -1000,7 +996,7 @@ public final class Ebean {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -1181,20 +1177,20 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxScope scope, TxRunnable r) {
|
||||
public static void execute(TxScope scope, Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(scope, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* Ebean.execute(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1203,17 +1199,17 @@ public final class Ebean {
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static void execute(TxRunnable r) {
|
||||
public static void execute(Runnable r) {
|
||||
serverMgr.getDefaultServer().execute(r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
* Execute a Callable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1223,7 +1219,7 @@ public final class Ebean {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* Ebean.execute(scope, new TxCallable<String>() {
|
||||
* Ebean.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1233,12 +1229,12 @@ public final class Ebean {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T execute(TxScope scope, TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(scope, c);
|
||||
public static <T> T executeCall(TxScope scope, Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(scope, c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
* Execute a Callable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1249,8 +1245,8 @@ public final class Ebean {
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxCallable<String>() {
|
||||
* public String call() {
|
||||
* Ebean.executeCall(() -> {
|
||||
*
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
@@ -1261,13 +1257,13 @@ public final class Ebean {
|
||||
* Ebean.save(u2);
|
||||
*
|
||||
* return u1.getEmail();
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public static <T> T execute(TxCallable<T> c) {
|
||||
return serverMgr.getDefaultServer().execute(c);
|
||||
public static <T> T executeCall(Callable<T> c) {
|
||||
return serverMgr.getDefaultServer().executeCall(c);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
@@ -16,6 +19,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -238,7 +242,7 @@ public interface EbeanServer {
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
|
||||
* query.setParameter("orderId", 2);
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // This is the same as:
|
||||
*
|
||||
@@ -247,7 +251,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
@@ -265,7 +269,7 @@ public interface EbeanServer {
|
||||
* predicates, order by, limits etc.
|
||||
* </p>
|
||||
* <p>
|
||||
* You then use findList(), findSet(), findMap() and findUnique() to execute
|
||||
* You then use findList(), findSet(), findMap() and findOne() to execute
|
||||
* the query and return the collection or bean.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -285,7 +289,7 @@ public interface EbeanServer {
|
||||
* .fetch("details")
|
||||
* .fetch("detail.product", "name")
|
||||
* .setId(2)
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // find some new orders ... with firstRow/maxRows
|
||||
* List<Order> orders =
|
||||
@@ -560,8 +564,8 @@ public interface EbeanServer {
|
||||
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
|
||||
* <p>
|
||||
* <p>
|
||||
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
|
||||
* {@link #execute(TxScope, TxRunnable)}.
|
||||
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
|
||||
* {@link #execute(TxScope, Runnable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <h3>REQUIRES_NEW example:</h3>
|
||||
@@ -694,7 +698,7 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <p>
|
||||
* If you want more control over the query then you can use createQuery() and
|
||||
* Query.findUnique();
|
||||
* Query.findOne();
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
@@ -714,7 +718,7 @@ public interface EbeanServer {
|
||||
* query.fetch("details.product", "name");
|
||||
*
|
||||
*
|
||||
* Order order = query.findUnique();
|
||||
* Order order = query.findOne();
|
||||
*
|
||||
* // traverse the object graph...
|
||||
*
|
||||
@@ -730,6 +734,7 @@ public interface EbeanServer {
|
||||
* @param beanType the type of entity bean to fetch
|
||||
* @param id the id value
|
||||
*/
|
||||
@Nullable
|
||||
<T> T find(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
@@ -773,6 +778,7 @@ public interface EbeanServer {
|
||||
* @param beanType the type of entity bean
|
||||
* @param id the id value
|
||||
*/
|
||||
@Nonnull
|
||||
<T> T getReference(Class<T> beanType, Object id);
|
||||
|
||||
/**
|
||||
@@ -788,6 +794,7 @@ public interface EbeanServer {
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -807,6 +814,7 @@ public interface EbeanServer {
|
||||
* @see Query#findEach(Consumer)
|
||||
* @see Query#findEachWhile(Predicate)
|
||||
*/
|
||||
@Nonnull
|
||||
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -843,7 +851,7 @@ public interface EbeanServer {
|
||||
* 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 QueryEachWhileConsumer.
|
||||
* results early by returning false for the Predicate.
|
||||
* </p>
|
||||
* <p>
|
||||
* Unlike findList() this is suitable for processing a query that will return
|
||||
@@ -883,6 +891,7 @@ public interface EbeanServer {
|
||||
* 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);
|
||||
|
||||
/**
|
||||
@@ -908,6 +917,7 @@ public interface EbeanServer {
|
||||
* @return the list of fetched beans.
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> List<T> findList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -923,6 +933,7 @@ public interface EbeanServer {
|
||||
* @return a Future object for the row count query
|
||||
* @see Query#findFutureCount()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -938,6 +949,7 @@ public interface EbeanServer {
|
||||
* @return a Future object for the list of Id's
|
||||
* @see Query#findFutureIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -954,6 +966,7 @@ public interface EbeanServer {
|
||||
* @return a Future object for the list result of the query
|
||||
* @see Query#findFutureList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -985,6 +998,7 @@ public interface EbeanServer {
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1010,6 +1024,7 @@ public interface EbeanServer {
|
||||
* @return the set of fetched beans.
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Set<T> findSet(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1026,6 +1041,7 @@ public interface EbeanServer {
|
||||
* @return the map of fetched beans.
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1058,6 +1074,7 @@ public interface EbeanServer {
|
||||
* @return the list of values for the selected property
|
||||
* @see Query#findSingleAttributeList()
|
||||
*/
|
||||
@Nonnull
|
||||
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1085,18 +1102,9 @@ public interface EbeanServer {
|
||||
/**
|
||||
* Similar to findOne() but returns an Optional (rather than nullable).
|
||||
*/
|
||||
@Nonnull
|
||||
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
<T> T findUnique(Query<T> query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute as a delete query deleting the 'root level' beans that match the predicates
|
||||
* in the query.
|
||||
@@ -1138,6 +1146,7 @@ public interface EbeanServer {
|
||||
* @return the list of fetched MapBean.
|
||||
* @see SqlQuery#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
@@ -1166,7 +1175,7 @@ public interface EbeanServer {
|
||||
* result.
|
||||
* </p>
|
||||
* <p>
|
||||
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
|
||||
* 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>
|
||||
@@ -1174,21 +1183,11 @@ public interface EbeanServer {
|
||||
* @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#findUnique()
|
||||
* @see SqlQuery#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findUnique(SqlQuery query, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Either Insert or Update the bean depending on its state.
|
||||
* <p>
|
||||
@@ -1585,7 +1584,7 @@ public interface EbeanServer {
|
||||
int execute(CallableSql callableSql, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with an explicit scope.
|
||||
* Execute a Runnable in a Transaction with an explicit scope.
|
||||
* <p>
|
||||
* The scope can control the transaction type, isolation and rollback
|
||||
* semantics.
|
||||
@@ -1596,7 +1595,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxRunnable() {
|
||||
* ebeanServer.execute(scope, new Runnable() {
|
||||
* public void run() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1605,10 +1604,10 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxScope scope, TxRunnable runnable);
|
||||
void execute(TxScope scope, Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction with the default scope.
|
||||
* Execute a Runnable in a Transaction with the default scope.
|
||||
* <p>
|
||||
* The default scope runs with REQUIRED and by default will rollback on any
|
||||
* exception (checked or runtime).
|
||||
@@ -1616,8 +1615,8 @@ public interface EbeanServer {
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* ebeanServer.execute(() -> {
|
||||
*
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
*
|
||||
@@ -1626,12 +1625,12 @@ public interface EbeanServer {
|
||||
*
|
||||
* ebeanServer.save(u1);
|
||||
* ebeanServer.save(u2);
|
||||
* }
|
||||
*
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
void execute(TxRunnable runnable);
|
||||
void execute(Runnable runnable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with an explicit scope.
|
||||
@@ -1645,7 +1644,7 @@ public interface EbeanServer {
|
||||
* // set specific transactional scope settings
|
||||
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
|
||||
*
|
||||
* ebeanServer.execute(scope, new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(scope, new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* ...
|
||||
@@ -1655,7 +1654,7 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T execute(TxScope scope, TxCallable<T> callable);
|
||||
<T> T executeCall(TxScope scope, Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction with the default scope.
|
||||
@@ -1664,13 +1663,9 @@ public interface EbeanServer {
|
||||
* exception (checked or runtime).
|
||||
* </p>
|
||||
* <p>
|
||||
* This is basically the same as TxRunnable except that it returns an Object
|
||||
* (and you specify the return type via generics).
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* ebeanServer.execute(new TxCallable<String>() {
|
||||
* ebeanServer.executeCall(new Callable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = ebeanServer.find(User.class, 1);
|
||||
* User u2 = ebeanServer.find(User.class, 2);
|
||||
@@ -1687,7 +1682,7 @@ public interface EbeanServer {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<T> T execute(TxCallable<T> callable);
|
||||
<T> T executeCall(Callable<T> callable);
|
||||
|
||||
/**
|
||||
* Return the manager of the server cache ("L2" cache).
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.common.SpiContainer;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.lib.ShutdownManager;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebean.service.SpiContainerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Iterator;
|
||||
import java.util.Properties;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Creates EbeanServer instances.
|
||||
@@ -30,8 +31,6 @@ import java.util.Properties;
|
||||
public class EbeanServerFactory {
|
||||
|
||||
|
||||
private static final String DEFAULT_CONTAINER = "io.ebeaninternal.server.core.DefaultContainer";
|
||||
|
||||
private static SpiContainer container;
|
||||
|
||||
/**
|
||||
@@ -66,10 +65,8 @@ public class EbeanServerFactory {
|
||||
|
||||
EbeanServer server = createInternal(config);
|
||||
|
||||
if (config.isDefaultServer()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
}
|
||||
if (config.isRegister()) {
|
||||
PrimaryServer.setSkip(true);
|
||||
Ebean.register(server, config.isDefaultServer());
|
||||
}
|
||||
|
||||
@@ -99,7 +96,7 @@ public class EbeanServerFactory {
|
||||
* </p>
|
||||
*/
|
||||
public static synchronized void shutdown() {
|
||||
ShutdownManager.shutdown();
|
||||
container.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -135,14 +132,10 @@ public class EbeanServerFactory {
|
||||
*/
|
||||
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
|
||||
|
||||
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
|
||||
|
||||
try {
|
||||
Class<?> cls = Class.forName(implClassName);
|
||||
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
|
||||
return (SpiContainer) constructor.newInstance(containerConfig);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
|
||||
if (factories.hasNext()) {
|
||||
return factories.next().create(containerConfig);
|
||||
}
|
||||
throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,11 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression icontains(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* In expression using pairs of value objects.
|
||||
*/
|
||||
Expression inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*/
|
||||
|
||||
@@ -5,8 +5,9 @@ import io.ebean.search.MultiMatch;
|
||||
import io.ebean.search.TextCommonTerms;
|
||||
import io.ebean.search.TextQueryString;
|
||||
import io.ebean.search.TextSimple;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
@@ -192,6 +193,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findList()
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
@@ -199,6 +201,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findIds()
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
|
||||
/**
|
||||
@@ -214,6 +217,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findSet()
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
|
||||
/**
|
||||
@@ -221,6 +225,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @see Query#findMap()
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
|
||||
/**
|
||||
@@ -253,8 +258,27 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findSingleAttributeList();
|
||||
|
||||
/**
|
||||
* Execute a query returning a single value of a single property/column.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String name =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .where().eq("id", 42)
|
||||
* .findSingleAttribute();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
default <A> A findSingleAttribute() {
|
||||
List<A> list = findSingleAttributeList();
|
||||
return !list.isEmpty() ? list.get(0) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -264,7 +288,7 @@ public interface ExpressionList<T> {
|
||||
* </p>
|
||||
*
|
||||
* @throws NonUniqueResultException if more than one result was found
|
||||
* @see Query#findUnique()
|
||||
* @see Query#findOne()
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
@@ -272,18 +296,9 @@ public interface ExpressionList<T> {
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
T findUnique();
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
@@ -294,6 +309,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the row count query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureRowCount<T> findFutureCount();
|
||||
|
||||
/**
|
||||
@@ -306,6 +322,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the list of Id's
|
||||
*/
|
||||
@Nonnull
|
||||
FutureIds<T> findFutureIds();
|
||||
|
||||
/**
|
||||
@@ -318,6 +335,7 @@ public interface ExpressionList<T> {
|
||||
*
|
||||
* @return a Future object for the list result of the query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
@@ -349,6 +367,7 @@ public interface ExpressionList<T> {
|
||||
* @return The PagedList
|
||||
* @see Query#findPagedList()
|
||||
*/
|
||||
@Nonnull
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
@@ -358,6 +377,7 @@ public interface ExpressionList<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
@@ -367,11 +387,13 @@ public interface ExpressionList<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
* Add some filter predicate expressions to the many property.
|
||||
*/
|
||||
@Nonnull
|
||||
ExpressionList<T> filterMany(String prop);
|
||||
|
||||
/**
|
||||
@@ -435,18 +457,37 @@ public interface ExpressionList<T> {
|
||||
Query<T> setMapKey(String mapKey);
|
||||
|
||||
/**
|
||||
* Set to true to use the query for executing this query.
|
||||
* Set to true when this query should use the bean cache.
|
||||
* <p>
|
||||
* This is now the same as setUseBeanCache(CacheMode.ON) and will be deprecated.
|
||||
* </p>
|
||||
*
|
||||
* @see Query#setUseCache(boolean)
|
||||
*/
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
|
||||
/**
|
||||
* Set to true to use the query for executing this query.
|
||||
* Set the mode to use the bean cache when executing this query.
|
||||
*
|
||||
* @see Query#setBeanCacheMode(CacheMode)
|
||||
*/
|
||||
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
|
||||
|
||||
/**
|
||||
* Set the {@link CacheMode} to use the query cache for executing this query.
|
||||
*
|
||||
* @see Query#setUseQueryCache(boolean)
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useCache);
|
||||
Query<T> setUseQueryCache(CacheMode useCache);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*
|
||||
* @see Query#setUseQueryCache(CacheMode)
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
@@ -780,6 +821,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> icontains(String propertyName, String value);
|
||||
|
||||
/**
|
||||
* In expression using pairs of value objects.
|
||||
*/
|
||||
ExpressionList<T> inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
*/
|
||||
@@ -795,6 +841,36 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> in(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* In - using a subQuery.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Query<?> subQuery) {
|
||||
return in(propertyName, subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Object... values) {
|
||||
return in(propertyName, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the collection of values.
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
default ExpressionList<T> isIn(String propertyName, Collection<?> values) {
|
||||
return in(propertyName, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not In - property has a value in the array of values.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -27,7 +28,7 @@ import java.util.List;
|
||||
* // Add your customer finder methods ...
|
||||
*
|
||||
* public Customer byName(String name) {
|
||||
* return query().eq("name", name).findUnique();
|
||||
* return query().eq("name", name).findOne();
|
||||
* }
|
||||
*
|
||||
* public List<Customer> findNew() {
|
||||
@@ -92,6 +93,20 @@ public class Finder<I, T> {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current transaction.
|
||||
*/
|
||||
public Transaction currentTransaction() {
|
||||
return db().currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the JDBC batch on the current transaction.
|
||||
*/
|
||||
public void flush() {
|
||||
db().flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
* <p>
|
||||
@@ -118,6 +133,7 @@ public class Finder<I, T> {
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
|
||||
*/
|
||||
@Nonnull
|
||||
public T ref(I id) {
|
||||
return db().getReference(type, id);
|
||||
}
|
||||
@@ -144,10 +160,33 @@ public class Finder<I, T> {
|
||||
/**
|
||||
* Retrieves all entities of the given type.
|
||||
*/
|
||||
@Nonnull
|
||||
public List<T> all() {
|
||||
return query().findList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an update query.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rows =
|
||||
* finder.update()
|
||||
* .set("status", Customer.Status.ACTIVE)
|
||||
* .set("updtime", new Timestamp(System.currentTimeMillis()))
|
||||
* .where()
|
||||
* .gt("id", 1000)
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Equivalent to {@link EbeanServer#update(Class)}
|
||||
*/
|
||||
public UpdateQuery<T> update() {
|
||||
return db().update(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a query.
|
||||
* <p>
|
||||
@@ -170,4 +209,5 @@ public class Finder<I, T> {
|
||||
public Query<T> query(String ormQuery) {
|
||||
return db().createQuery(type, ormQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@@ -109,11 +110,13 @@ public interface PagedList<T> {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Nonnull
|
||||
Future<Integer> getFutureCount();
|
||||
|
||||
/**
|
||||
* Return the list of entities for this page.
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> getList();
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a list of value object pairs.
|
||||
* <p>
|
||||
* This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the
|
||||
* IN clause is not a single property but instead a pair of properties.
|
||||
* </p>
|
||||
* <p>
|
||||
* These queries can have predicates that can be translated into a list of complex natural keys such that the L2
|
||||
* cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // where a bean is annotated with a complex
|
||||
* // natural key made of several properties
|
||||
* @Cache(naturalKey = {"store","code","sku"})
|
||||
*
|
||||
*
|
||||
* Pairs pairs = new Pairs("sku", "code");
|
||||
* pairs.add("sj2", 1000);
|
||||
* pairs.add("sj2", 1001);
|
||||
* pairs.add("pf3", 1000);
|
||||
*
|
||||
* List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
|
||||
* .where()
|
||||
* .eq("store", "def")
|
||||
* .inPairs(pairs) // IN clause with 'pairs' of values
|
||||
* .orderBy("sku desc")
|
||||
*
|
||||
* // query expressions cover the natural key properties
|
||||
* // so we can choose to hit the L2 bean cache if we want
|
||||
* .setUseCache(true)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
* <h3>Important implementation Note</h3>
|
||||
* <p>
|
||||
* When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the
|
||||
* pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this
|
||||
* on any DB that supports expression/formula based indexes.
|
||||
* using a DB string concatenation formula
|
||||
* </p>
|
||||
* <p>
|
||||
* This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a
|
||||
* DB concatenation formula to match. We see SQL like:
|
||||
* </p>
|
||||
* <pre>{@code sql
|
||||
*
|
||||
* ...
|
||||
* where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? )
|
||||
*
|
||||
* // bind values like: "sj2-1000", "pf3-1000"
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* We often create a DB expression index to match the DB concat formula like:
|
||||
* </p>
|
||||
* <pre>{@code sql
|
||||
*
|
||||
* create index ix_name on table_name ((sku || '-' || code));
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public class Pairs {
|
||||
|
||||
private final String property0;
|
||||
private final String property1;
|
||||
|
||||
private final List<Entry> entries = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Character between the values when combined via DB varchar concatenation.
|
||||
*/
|
||||
private String concatSeparator = "-";
|
||||
|
||||
/**
|
||||
* Optional suffix added to DB varchar concatenation formula.
|
||||
*/
|
||||
private String concatSuffix;
|
||||
|
||||
/**
|
||||
* Create with 2 property names.
|
||||
*
|
||||
* @param property0 The property of the first value
|
||||
* @param property1 The property of the second value
|
||||
*/
|
||||
public Pairs(String property0, String property1) {
|
||||
this.property0 = property0;
|
||||
this.property1 = property1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a pair of value objects.
|
||||
* <p>
|
||||
* Both values are expected to be immutable with equals and hashCode implementations.
|
||||
* </p>
|
||||
*
|
||||
* @param a Value of the first property
|
||||
* @param b Value of the second property
|
||||
*/
|
||||
public Pairs add(Object a, Object b) {
|
||||
entries.add(new Entry(a, b));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first property name.
|
||||
*/
|
||||
public String getProperty0() {
|
||||
return property0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the second property name.
|
||||
*/
|
||||
public String getProperty1() {
|
||||
return property1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the value pairs.
|
||||
*/
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the separator character used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public String getConcatSeparator() {
|
||||
return concatSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the separator character used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public Pairs setConcatSeparator(String concatSeparator) {
|
||||
this.concatSeparator = concatSeparator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a suffix used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public String getConcatSuffix() {
|
||||
return concatSuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suffix used with DB varchar concatenation to combine the 2 values.
|
||||
*/
|
||||
public Pairs setConcatSuffix(String concatSuffix) {
|
||||
this.concatSuffix = concatSuffix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of 2 value objects.
|
||||
* <p>
|
||||
* Used to support inPairs() expression.
|
||||
*/
|
||||
public static class Entry {
|
||||
|
||||
private final Object a;
|
||||
private final Object b;
|
||||
|
||||
/**
|
||||
* Create with values for property0 and property1 respectively.
|
||||
*
|
||||
* @param a Value of the first property
|
||||
* @param b Value of the second property
|
||||
*/
|
||||
public Entry(Object a, Object b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "{" + a + "," + b + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the first property.
|
||||
*/
|
||||
public Object getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for the second property.
|
||||
*/
|
||||
public Object getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Entry that = (Entry) o;
|
||||
return a.equals(that.a) && b.equals(that.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = a.hashCode();
|
||||
result = 92821 * result + b.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,8 @@ class PrimaryServer {
|
||||
private static String determineDefaultServerName() {
|
||||
|
||||
String defaultServerName = System.getenv("EBEAN_DB");
|
||||
defaultServerName = System.getProperty("db", defaultServerName);
|
||||
defaultServerName = System.getProperty("ebean_db", defaultServerName);
|
||||
if (isEmpty(defaultServerName)) {
|
||||
defaultServerName = System.getProperty("datasource.default");
|
||||
if (isEmpty(defaultServerName)) {
|
||||
@@ -74,6 +76,7 @@ class PrimaryServer {
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = "db";
|
||||
}
|
||||
System.setProperty("ebean_db", defaultServerName);
|
||||
return defaultServerName;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* A location for profiling transactions and queries.
|
||||
* <p>
|
||||
* Typically represents a class method in the form of class file and line of code that started
|
||||
* the transaction or invoked the query.
|
||||
* </p>
|
||||
*/
|
||||
public interface ProfileLocation {
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation.
|
||||
*/
|
||||
static ProfileLocation create() {
|
||||
return XServiceProvider.profileLocationFactory().create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation with a given lineNumber and label.
|
||||
*/
|
||||
static ProfileLocation create(int lineNumber, String label) {
|
||||
return XServiceProvider.profileLocationFactory().create(lineNumber, label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new ProfileLocation with a given location.
|
||||
*/
|
||||
static ProfileLocation createAt(String location) {
|
||||
return XServiceProvider.profileLocationFactory().createAt(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the location description.
|
||||
*/
|
||||
String obtain();
|
||||
|
||||
/**
|
||||
* Return a short version of the location description.
|
||||
*/
|
||||
String shortDescription();
|
||||
|
||||
/**
|
||||
* Add execution time.
|
||||
*/
|
||||
void add(long executionTime);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
@@ -215,11 +215,6 @@ public interface Query<T> {
|
||||
SKIPLOCKED
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSql that was set to use for this query.
|
||||
*/
|
||||
RawSql getRawSql();
|
||||
|
||||
/**
|
||||
* Set RawSql to use for this query.
|
||||
*/
|
||||
@@ -602,6 +597,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @see EbeanServer#findIds(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findIds();
|
||||
|
||||
/**
|
||||
@@ -642,6 +638,7 @@ public interface Query<T> {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Nonnull
|
||||
QueryIterator<T> findIterate();
|
||||
|
||||
/**
|
||||
@@ -669,8 +666,7 @@ public interface Query<T> {
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the QueryEachConsumer (SAM) interface which is better suited to use
|
||||
* with Java8 closures.
|
||||
* iterator uses the Consumer interface which is better suited to use with Java8 closures.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -699,8 +695,7 @@ public interface Query<T> {
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
|
||||
* with Java8 closures.
|
||||
* iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
@@ -740,6 +735,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @see EbeanServer#findList(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
@@ -758,6 +754,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @see EbeanServer#findSet(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
Set<T> findSet();
|
||||
|
||||
/**
|
||||
@@ -780,6 +777,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @see EbeanServer#findMap(Query, Transaction)
|
||||
*/
|
||||
@Nonnull
|
||||
<K> Map<K, T> findMap();
|
||||
|
||||
/**
|
||||
@@ -812,8 +810,24 @@ public interface Query<T> {
|
||||
*
|
||||
* @return the list of values for the selected property
|
||||
*/
|
||||
@Nonnull
|
||||
<A> List<A> findSingleAttributeList();
|
||||
|
||||
/**
|
||||
* Execute a query returning a single value of a single property/column.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String name =
|
||||
* Ebean.find(Customer.class)
|
||||
* .select("name")
|
||||
* .where().eq("id", 42)
|
||||
* .findSingleAttribute();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
<A> A findSingleAttribute();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
@@ -831,7 +845,7 @@ public interface Query<T> {
|
||||
* Product product =
|
||||
* ebeanServer.find(Product.class)
|
||||
* .where().eq("sku", "aa113")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
* ...
|
||||
* }</pre>
|
||||
* <p>
|
||||
@@ -845,7 +859,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly loaded
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -860,18 +874,9 @@ public interface Query<T> {
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
* <p>
|
||||
* This proceeded findOne which was introduced to better match spring data.
|
||||
* This will be deprecated at some future point.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
T findUnique();
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
@@ -884,6 +889,7 @@ public interface Query<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersions();
|
||||
|
||||
/**
|
||||
@@ -893,6 +899,7 @@ public interface Query<T> {
|
||||
* It will execute the query against the history returning the versions of the bean.
|
||||
* </p>
|
||||
*/
|
||||
@Nonnull
|
||||
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
|
||||
|
||||
/**
|
||||
@@ -930,6 +937,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the row count query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureRowCount<T> findFutureCount();
|
||||
|
||||
/**
|
||||
@@ -942,6 +950,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the list of Id's
|
||||
*/
|
||||
@Nonnull
|
||||
FutureIds<T> findFutureIds();
|
||||
|
||||
/**
|
||||
@@ -953,6 +962,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return a Future object for the list result of the query
|
||||
*/
|
||||
@Nonnull
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
@@ -982,6 +992,7 @@ public interface Query<T> {
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
@Nonnull
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
@@ -1028,7 +1039,7 @@ public interface Query<T> {
|
||||
Query<T> setParameter(int position, Object value);
|
||||
|
||||
/**
|
||||
* Set the Id value to query. This is used with findUnique().
|
||||
* Set the Id value to query. This is used with findOne().
|
||||
* <p>
|
||||
* You can use this to have further control over the query. For example adding
|
||||
* fetch joins.
|
||||
@@ -1039,7 +1050,7 @@ public interface Query<T> {
|
||||
* ebeanServer.find(Order.class)
|
||||
* .setId(1)
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly fetched
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
@@ -1294,7 +1305,7 @@ public interface Query<T> {
|
||||
*
|
||||
* // Assuming sku is unique for products...
|
||||
*
|
||||
* Map<?,Product> productMap =
|
||||
* Map<String,Product> productMap =
|
||||
* ebeanServer.find(Product.class)
|
||||
* // use sku for keys...
|
||||
* .setMapKey("sku")
|
||||
@@ -1309,22 +1320,60 @@ public interface Query<T> {
|
||||
/**
|
||||
* Set this to false to not use the bean cache.
|
||||
* <p>
|
||||
* This method is now superseded by {@link #setBeanCacheMode(CacheMode)}
|
||||
* which provides more explicit options controlled bean cache use.
|
||||
* </p>
|
||||
* <p>
|
||||
* This method is likely to be deprecated in the future with migration
|
||||
* over to setUseBeanCache().
|
||||
* </p>
|
||||
*/
|
||||
default Query<T> setUseCache(boolean useCache) {
|
||||
return setBeanCacheMode(useCache ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mode to use the bean cache when executing this query.
|
||||
* <p>
|
||||
* By default "find by id" and "find by natural key" will use the bean cache
|
||||
* when bean caching is enabled. Setting this to false means that the query
|
||||
* will not use the bean cache and instead hit the database.
|
||||
* </p>
|
||||
* <p>
|
||||
* In the case of other queries (findList(), findEach() etc) then setting this to
|
||||
* false beans that the if lazy loading is invoked that lazy loading will not try
|
||||
* to use the bean cache.
|
||||
* By default findList() with natural keys will not use the bean cache. In that
|
||||
* case we need to explicitly use the bean cache.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setUseCache(boolean useCache);
|
||||
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
|
||||
|
||||
/**
|
||||
* Set this to true to use the query cache.
|
||||
* Set the {@link CacheMode} to use the query for executing this query.
|
||||
*/
|
||||
Query<T> setUseQueryCache(boolean useQueryCache);
|
||||
Query<T> setUseQueryCache(CacheMode queryCacheMode);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
*/
|
||||
default Query<T> setUseQueryCache(boolean enabled) {
|
||||
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an id to identify this query for profiling purposes.
|
||||
* <p>
|
||||
* The profileId is expected to be unique for a given bean type.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that the profileId is treated as a short internally and has a MAX value of 32,767.
|
||||
* </p>
|
||||
*/
|
||||
Query<T> setProfileId(int profileId);
|
||||
|
||||
/**
|
||||
* Set the profile location of this query. This is used to relate query execution metrics
|
||||
* back to a location like a specific line of code.
|
||||
*/
|
||||
Query<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set to true if this query should execute against the doc store.
|
||||
@@ -1340,8 +1389,9 @@ public interface Query<T> {
|
||||
Query<T> setReadOnly(boolean readOnly);
|
||||
|
||||
/**
|
||||
* When set to true all the beans from this query are loaded into the bean
|
||||
* cache.
|
||||
* Will be deprecated - migrate to use setBeanCacheMode(CacheMode.RECACHE).
|
||||
* <p>
|
||||
* When set to true all the beans from this query are loaded into the bean cache.
|
||||
*/
|
||||
Query<T> setLoadBeanCache(boolean loadBeanCache);
|
||||
|
||||
@@ -1386,9 +1436,12 @@ public interface Query<T> {
|
||||
String getGeneratedSql();
|
||||
|
||||
/**
|
||||
* Deprecated - migrate to forUpdate().
|
||||
*
|
||||
* executed the select with "for update" which should lock the record
|
||||
* "on read"
|
||||
*/
|
||||
@Deprecated
|
||||
Query<T> setForUpdate(boolean forUpdate);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.util.CamelCaseHelper;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Used to build object graphs based on a raw SQL statement (rather than
|
||||
* generated by Ebean).
|
||||
@@ -169,566 +158,6 @@ import java.util.Map;
|
||||
* Note that lazy loading also works with object graphs built with RawSql.
|
||||
* </p>
|
||||
*/
|
||||
public final class RawSql implements Serializable {
|
||||
public interface RawSql {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
/**
|
||||
* Construct with a ResultSet and properties that the columns map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
* <p>
|
||||
* When a query executes this RawSql object then it will close the resultSet.
|
||||
*/
|
||||
public RawSql(ResultSet resultSet, String... propertyNames) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = null;
|
||||
this.columnMapping = new ColumnMapping(propertyNames);
|
||||
}
|
||||
|
||||
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Sql either unparsed or in parsed (broken up) form.
|
||||
*/
|
||||
public Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the key;
|
||||
*/
|
||||
public Key getKey() {
|
||||
boolean parsed = sql != null && sql.parsed;
|
||||
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
|
||||
return new Key(parsed, unParsedSql, columnMapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resultSet if this is a ResultSet based RawSql.
|
||||
*/
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping for the SQL columns to bean properties.
|
||||
*/
|
||||
public ColumnMapping getColumnMapping() {
|
||||
return columnMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the sql part of the query. For parsed RawSql the sql is broken
|
||||
* up so that Ebean can insert extra WHERE and HAVING expressions into the
|
||||
* SQL.
|
||||
*/
|
||||
public static final class Sql implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean parsed;
|
||||
|
||||
private final String unparsedSql;
|
||||
|
||||
private final String preFrom;
|
||||
|
||||
private final String preWhere;
|
||||
|
||||
private final boolean andWhereExpr;
|
||||
|
||||
private final String preHaving;
|
||||
|
||||
private final boolean andHavingExpr;
|
||||
|
||||
private final String orderByPrefix;
|
||||
|
||||
private final String orderBy;
|
||||
|
||||
private final boolean distinct;
|
||||
|
||||
/**
|
||||
* Construct for unparsed SQL.
|
||||
*/
|
||||
protected Sql(String unparsedSql) {
|
||||
this.parsed = false;
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.preFrom = null;
|
||||
this.preHaving = null;
|
||||
this.preWhere = null;
|
||||
this.andHavingExpr = false;
|
||||
this.andWhereExpr = false;
|
||||
this.orderByPrefix = null;
|
||||
this.orderBy = null;
|
||||
this.distinct = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for parsed SQL.
|
||||
*/
|
||||
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
|
||||
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
|
||||
|
||||
this.unparsedSql = unparsedSql;
|
||||
this.parsed = true;
|
||||
this.preFrom = preFrom;
|
||||
this.preHaving = preHaving;
|
||||
this.preWhere = preWhere;
|
||||
this.andHavingExpr = andHavingExpr;
|
||||
this.andWhereExpr = andWhereExpr;
|
||||
this.orderByPrefix = orderByPrefix;
|
||||
this.orderBy = orderBy;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (!parsed) {
|
||||
return "unparsed[" + unparsedSql + "]";
|
||||
}
|
||||
return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]";
|
||||
}
|
||||
|
||||
public boolean isDistinct() {
|
||||
return distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the SQL is left completely unmodified.
|
||||
* <p>
|
||||
* This means Ebean can't add WHERE or HAVING expressions into the query -
|
||||
* it will be left completely unmodified.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL when it is unparsed.
|
||||
*/
|
||||
public String getUnparsedSql() {
|
||||
return unparsedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to FROM clause.
|
||||
*/
|
||||
public String getPreFrom() {
|
||||
return preFrom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to WHERE clause.
|
||||
*/
|
||||
public String getPreWhere() {
|
||||
return preWhere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is already a WHERE clause and any extra where
|
||||
* expressions start with AND.
|
||||
*/
|
||||
public boolean isAndWhereExpr() {
|
||||
return andWhereExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL prior to HAVING clause.
|
||||
*/
|
||||
public String getPreHaving() {
|
||||
return preHaving;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is already a HAVING clause and any extra having
|
||||
* expressions start with AND.
|
||||
*/
|
||||
public boolean isAndHavingExpr() {
|
||||
return andHavingExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'order by' keywords.
|
||||
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
|
||||
*/
|
||||
public String getOrderByPrefix() {
|
||||
return (orderByPrefix == null) ? "order by" : orderByPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL ORDER BY clause.
|
||||
*/
|
||||
public String getOrderBy() {
|
||||
return orderBy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the column mapping for raw sql DB columns to bean properties.
|
||||
*/
|
||||
public static final class ColumnMapping implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final LinkedHashMap<String, Column> dbColumnMap;
|
||||
|
||||
private final Map<String, String> propertyMap;
|
||||
|
||||
private final Map<String, Column> propertyColumnMap;
|
||||
|
||||
private final boolean parsed;
|
||||
|
||||
private final boolean immutable;
|
||||
|
||||
/**
|
||||
* Construct from parsed sql where the columns have been identified.
|
||||
*/
|
||||
protected ColumnMapping(List<Column> columns) {
|
||||
this.immutable = false;
|
||||
this.parsed = true;
|
||||
this.propertyMap = null;
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
for (Column c : columns) {
|
||||
dbColumnMap.put(c.getDbColumnKey(), c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for unparsed sql.
|
||||
*/
|
||||
protected ColumnMapping() {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for ResultSet use.
|
||||
*/
|
||||
protected ColumnMapping(String... propertyNames) {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<>();
|
||||
|
||||
int pos = 0;
|
||||
for (String prop : propertyNames) {
|
||||
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
|
||||
}
|
||||
propertyColumnMap = dbColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an immutable ColumnMapping based on collected information.
|
||||
*/
|
||||
protected ColumnMapping(boolean parsed, LinkedHashMap<String, Column> dbColumnMap) {
|
||||
this.immutable = true;
|
||||
this.parsed = parsed;
|
||||
this.dbColumnMap = dbColumnMap;
|
||||
|
||||
HashMap<String, Column> pcMap = new HashMap<>();
|
||||
HashMap<String, String> pMap = new HashMap<>();
|
||||
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
pMap.put(c.getPropertyName(), c.getDbColumn());
|
||||
pcMap.put(c.getPropertyName(), c);
|
||||
}
|
||||
this.propertyMap = Collections.unmodifiableMap(pMap);
|
||||
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ColumnMapping that = (ColumnMapping) o;
|
||||
return dbColumnMap.equals(that.dbColumnMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return dbColumnMap.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is mapped.
|
||||
*/
|
||||
public boolean contains(String property) {
|
||||
return this.propertyColumnMap.containsKey(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an immutable copy of this ColumnMapping.
|
||||
*
|
||||
* @throws IllegalStateException when a propertyName has not been defined for a column.
|
||||
*/
|
||||
protected ColumnMapping createImmutableCopy() {
|
||||
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
c.checkMapping();
|
||||
}
|
||||
|
||||
return new ColumnMapping(parsed, dbColumnMap);
|
||||
}
|
||||
|
||||
protected void columnMapping(String dbColumn, String propertyName) {
|
||||
|
||||
if (immutable) {
|
||||
throw new IllegalStateException("Should never happen");
|
||||
}
|
||||
if (!parsed) {
|
||||
int pos = dbColumnMap.size();
|
||||
dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName));
|
||||
} else {
|
||||
Column column = dbColumnMap.get(dbColumn);
|
||||
if (column == null) {
|
||||
String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
column.setPropertyName(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the Columns where supplied by parsing the sql select
|
||||
* clause.
|
||||
* <p>
|
||||
* In the case where the columns where parsed then we can do extra checks on
|
||||
* the column mapping such as, is the column a valid one in the sql and
|
||||
* whether all the columns in the sql have been mapped.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isParsed() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of columns in this column mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return dbColumnMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping.
|
||||
*/
|
||||
protected Map<String, Column> mapping() {
|
||||
return dbColumnMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping by DB column.
|
||||
*/
|
||||
public Map<String, String> getMapping() {
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index position by bean property name.
|
||||
*/
|
||||
public int getIndexPosition(String property) {
|
||||
Column c = propertyColumnMap.get(property);
|
||||
return c == null ? -1 : c.getIndexPos();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an iterator of the Columns.
|
||||
*/
|
||||
public Iterator<Column> getColumns() {
|
||||
return dbColumnMap.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
* <p>
|
||||
* For example modify all mappings with table alias "c" to have the path prefix "customer".
|
||||
* </p>
|
||||
* <p>
|
||||
* For the "Root type" you don't need to specify a tableAliasMapping.
|
||||
* </p>
|
||||
*/
|
||||
public void tableAliasMapping(String tableAlias, String path) {
|
||||
|
||||
String startMatch = tableAlias + ".";
|
||||
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
|
||||
if (entry.getKey().startsWith(startMatch)) {
|
||||
entry.getValue().tableAliasMapping(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Column of the RawSql that is mapped to a bean property (or ignored).
|
||||
*/
|
||||
public static class Column implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final int indexPos;
|
||||
private final String dbColumn;
|
||||
|
||||
private final String dbAlias;
|
||||
|
||||
private String propertyName;
|
||||
|
||||
/**
|
||||
* Construct a Column.
|
||||
*/
|
||||
public Column(int indexPos, String dbColumn, String dbAlias) {
|
||||
this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn));
|
||||
}
|
||||
|
||||
private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) {
|
||||
this.indexPos = indexPos;
|
||||
this.dbColumn = dbColumn;
|
||||
this.dbAlias = dbAlias;
|
||||
if (propertyName == null && dbAlias != null) {
|
||||
this.propertyName = dbAlias;
|
||||
} else {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
protected static String derivePropertyName(String dbAlias, String dbColumn) {
|
||||
if (dbAlias != null) {
|
||||
return CamelCaseHelper.toCamelFromUnderscore(dbAlias);
|
||||
}
|
||||
int dotPos = dbColumn.indexOf('.');
|
||||
if (dotPos > -1) {
|
||||
dbColumn = dbColumn.substring(dotPos + 1);
|
||||
}
|
||||
return CamelCaseHelper.toCamelFromUnderscore(dbColumn);
|
||||
}
|
||||
|
||||
private void checkMapping() {
|
||||
if (propertyName == null) {
|
||||
String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Column that = (Column) o;
|
||||
if (indexPos != that.indexPos) return false;
|
||||
if (!dbColumn.equals(that.dbColumn)) return false;
|
||||
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
|
||||
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = indexPos;
|
||||
result = 92821 * result + dbColumn.hashCode();
|
||||
result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
|
||||
result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dbColumn + "->" + propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index position of this column.
|
||||
*/
|
||||
public int getIndexPos() {
|
||||
return indexPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column alias if specified otherwise DB column.
|
||||
* This is used as the key for mapping a column to a logical property.
|
||||
*/
|
||||
public String getDbColumnKey() {
|
||||
return (dbAlias != null) ? dbAlias : dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column name including table alias (if it has one).
|
||||
*/
|
||||
public String getDbColumn() {
|
||||
return dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean property this column is mapped to.
|
||||
*/
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property name mapped to this db column.
|
||||
*/
|
||||
private void setPropertyName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the path to the property name.
|
||||
* <p/>
|
||||
* For example if path is "customer" then "name" becomes "customer.name".
|
||||
*/
|
||||
public void tableAliasMapping(String path) {
|
||||
if (path != null) {
|
||||
propertyName = path + "." + propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A key for the RawSql object using for the query plan.
|
||||
*/
|
||||
public static final class Key {
|
||||
|
||||
private final boolean parsed;
|
||||
private final ColumnMapping columnMapping;
|
||||
private final String unParsedSql;
|
||||
|
||||
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
|
||||
this.parsed = parsed;
|
||||
this.unParsedSql = unParsedSql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Key that = (Key) o;
|
||||
return parsed == that.parsed
|
||||
&& columnMapping.equals(that.columnMapping)
|
||||
&& unParsedSql.equals(that.unParsedSql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (parsed ? 1 : 0);
|
||||
result = 92821 * result + columnMapping.hashCode();
|
||||
result = 92821 * result + unParsedSql.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.RawSql.ColumnMapping;
|
||||
import io.ebean.RawSql.Sql;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
@@ -14,18 +11,7 @@ import java.sql.ResultSet;
|
||||
*
|
||||
* @see RawSql
|
||||
*/
|
||||
public class RawSqlBuilder {
|
||||
|
||||
/**
|
||||
* Special property name assigned to a DB column that should be ignored.
|
||||
*/
|
||||
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
public interface RawSqlBuilder {
|
||||
|
||||
/**
|
||||
* Create and return a RawSql object based on the resultSet and list of properties the columns in
|
||||
@@ -34,8 +20,8 @@ public class RawSqlBuilder {
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
*/
|
||||
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return new RawSql(resultSet, propertyNames);
|
||||
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,10 +29,8 @@ public class RawSqlBuilder {
|
||||
* modified - so no additional WHERE or HAVING expressions can be added to
|
||||
* this query.
|
||||
*/
|
||||
public static RawSqlBuilder unparsed(String sql) {
|
||||
|
||||
Sql s = new Sql(sql);
|
||||
return new RawSqlBuilder(s, new ColumnMapping());
|
||||
static RawSqlBuilder unparsed(String sql) {
|
||||
return XServiceProvider.rawSql().unparsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,19 +46,8 @@ public class RawSqlBuilder {
|
||||
* correct column names are entered into the mapping.
|
||||
* </p>
|
||||
*/
|
||||
public static RawSqlBuilder parse(String sql) {
|
||||
|
||||
Sql sql2 = DRawSqlParser.parse(sql);
|
||||
String select = sql2.getPreFrom();
|
||||
|
||||
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
|
||||
return new RawSqlBuilder(sql2, mapping);
|
||||
}
|
||||
|
||||
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
this.resultSet = null;
|
||||
static RawSqlBuilder parse(String sql) {
|
||||
return XServiceProvider.rawSql().parsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,17 +60,12 @@ public class RawSqlBuilder {
|
||||
* @param dbColumn the DB column that we are mapping to a bean property
|
||||
* @param propertyName the bean property that we are mapping the DB column to.
|
||||
*/
|
||||
public RawSqlBuilder columnMapping(String dbColumn, String propertyName) {
|
||||
columnMapping.columnMapping(dbColumn, propertyName);
|
||||
return this;
|
||||
}
|
||||
RawSqlBuilder columnMapping(String dbColumn, String propertyName);
|
||||
|
||||
/**
|
||||
* Ignore this DB column. It is not mapped to any bean property.
|
||||
*/
|
||||
public RawSqlBuilder columnMappingIgnore(String dbColumn) {
|
||||
return columnMapping(dbColumn, IGNORE_COLUMN);
|
||||
}
|
||||
RawSqlBuilder columnMappingIgnore(String dbColumn);
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
@@ -108,24 +76,12 @@ public class RawSqlBuilder {
|
||||
* For the "Root type" you don't need to specify a tableAliasMapping.
|
||||
* </p>
|
||||
*/
|
||||
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
|
||||
columnMapping.tableAliasMapping(tableAlias, path);
|
||||
return this;
|
||||
}
|
||||
RawSqlBuilder tableAliasMapping(String tableAlias, String path);
|
||||
|
||||
/**
|
||||
* Create the immutable RawSql object. Do this after all the column mapping
|
||||
* has been defined.
|
||||
*/
|
||||
public RawSql create() {
|
||||
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the internal parsed Sql object (for testing).
|
||||
*/
|
||||
protected Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
RawSql create();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -43,6 +45,7 @@ public interface SqlQuery extends Serializable {
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
@Nonnull
|
||||
List<SqlRow> findList();
|
||||
|
||||
/**
|
||||
@@ -71,18 +74,15 @@ public interface SqlQuery extends Serializable {
|
||||
* PersistenceException.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Synonym for findOne().
|
||||
*/
|
||||
SqlRow findUnique();
|
||||
|
||||
/**
|
||||
* The same as bind for named parameters.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.config.DocStoreConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
@@ -12,6 +13,23 @@ import java.sql.Connection;
|
||||
*/
|
||||
public interface Transaction extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Return the current transaction (of the default server) or null if there is
|
||||
* no current transaction in scope.
|
||||
* <p>
|
||||
* This is the same as <code>Ebean.currentTransaction()</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* 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()
|
||||
*/
|
||||
static Transaction current() {
|
||||
return Ebean.currentTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read Committed transaction isolation. Same as
|
||||
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
|
||||
@@ -41,6 +59,14 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void register(TransactionCallback callback);
|
||||
|
||||
/**
|
||||
* Set a label on the transaction.
|
||||
* <p>
|
||||
* This label is used to group transaction execution times for performance metrics reporting.
|
||||
* </p>
|
||||
*/
|
||||
void setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return true if this transaction is read only.
|
||||
*/
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Execute a TxCallable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxCallable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this is basically the same as TxRunnable except that it returns an
|
||||
* Object (and you specify the return type via generics).
|
||||
* </p>
|
||||
* <p>
|
||||
* See also {@link TxRunnable}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* Ebean.execute(new TxCallable<String>() {
|
||||
* public String call() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
* u1.setName("u1 mod");
|
||||
* u2.setName("u2 mod");
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
*
|
||||
* return u1.getEmail();
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see TxRunnable
|
||||
*/
|
||||
public interface TxCallable<T> {
|
||||
|
||||
/**
|
||||
* Execute the method within a transaction scope returning the result.
|
||||
* <p>
|
||||
* If you do not want to return a result you should look to use TxRunnable
|
||||
* instead.
|
||||
* </p>
|
||||
*/
|
||||
T call();
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Execute a TxRunnable in a Transaction scope.
|
||||
* <p>
|
||||
* Use this with the {@link Ebean#execute(TxRunnable)} method.
|
||||
* </p>
|
||||
* <p>
|
||||
* See also {@link TxCallable}.
|
||||
* </p>
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // this run method runs in a transaction scope
|
||||
* // which by default is TxScope.REQUIRED
|
||||
*
|
||||
* Ebean.execute(new TxRunnable() {
|
||||
* public void run() {
|
||||
* User u1 = Ebean.find(User.class, 1);
|
||||
* User u2 = Ebean.find(User.class, 2);
|
||||
*
|
||||
* u1.setName("u1 mod");
|
||||
* u2.setName("u2 mod");
|
||||
*
|
||||
* Ebean.save(u1);
|
||||
* Ebean.save(u2);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see TxCallable
|
||||
*/
|
||||
public interface TxRunnable {
|
||||
|
||||
/**
|
||||
* Run the method in a transaction sope.
|
||||
*/
|
||||
void run();
|
||||
}
|
||||
@@ -1,46 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.TxIsolation;
|
||||
import io.ebean.annotation.TxType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Holds the definition of how a transactional method should run.
|
||||
* <p>
|
||||
* This information matches the features of the Transactional annotation. You
|
||||
* can use it directly with TxRunnable or TxCallable via
|
||||
* {@link Ebean#execute(TxScope, TxCallable)} or
|
||||
* {@link Ebean#execute(TxScope, TxRunnable)}.
|
||||
* can use it directly with Runnable or Callable via
|
||||
* {@link Ebean#execute(TxScope, Runnable)} or
|
||||
* {@link Ebean#executeCall(TxScope, Callable)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This object is used internally with the enhancement of a method with
|
||||
* Transactional annotation.
|
||||
* </p>
|
||||
*
|
||||
* @see TxCallable
|
||||
* @see TxRunnable
|
||||
* @see Ebean#execute(TxScope, TxCallable)
|
||||
* @see Ebean#execute(TxScope, TxRunnable)
|
||||
* @see Ebean#execute(TxScope, Runnable)
|
||||
* @see Ebean#executeCall(TxScope, Callable)
|
||||
*/
|
||||
public final class TxScope {
|
||||
|
||||
TxType type;
|
||||
private int profileId;
|
||||
|
||||
String serverName;
|
||||
private TxType type;
|
||||
|
||||
TxIsolation isolation;
|
||||
private String serverName;
|
||||
|
||||
PersistBatch batch;
|
||||
private TxIsolation isolation;
|
||||
|
||||
PersistBatch batchOnCascade;
|
||||
private PersistBatch batch;
|
||||
|
||||
int batchSize;
|
||||
private PersistBatch batchOnCascade;
|
||||
|
||||
boolean skipGeneratedKeys;
|
||||
private int batchSize;
|
||||
|
||||
boolean readOnly;
|
||||
private boolean skipGeneratedKeys;
|
||||
|
||||
ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
private boolean readOnly;
|
||||
|
||||
ArrayList<Class<? extends Throwable>> noRollbackFor;
|
||||
/**
|
||||
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
|
||||
*/
|
||||
private boolean flushOnQuery = true;
|
||||
|
||||
private boolean skipCache;
|
||||
|
||||
private String label;
|
||||
|
||||
private ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
|
||||
private ArrayList<Class<? extends Throwable>> noRollbackFor;
|
||||
|
||||
private ProfileLocation profileLocation;
|
||||
|
||||
/**
|
||||
* Helper method to create a TxScope with REQUIRES.
|
||||
@@ -160,6 +176,66 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction profile id.
|
||||
*/
|
||||
public int getProfileId() {
|
||||
return profileId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction profile id.
|
||||
*/
|
||||
public TxScope setProfileId(int profileId) {
|
||||
this.profileId = profileId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the profile location.
|
||||
*/
|
||||
public TxScope setProfileLocation(ProfileLocation profileLocation) {
|
||||
this.profileLocation = profileLocation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the L2 cache should be skipped for this transaction.
|
||||
*/
|
||||
public boolean isSkipCache() {
|
||||
return skipCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the transaction should skip L2 cache access.
|
||||
*/
|
||||
public TxScope setSkipCache(boolean skipCache) {
|
||||
this.skipCache = skipCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the label for the transaction.
|
||||
*/
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a label for the transaction.
|
||||
*/
|
||||
public TxScope setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch mode.
|
||||
*/
|
||||
@@ -235,6 +311,28 @@ public final class TxScope {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
|
||||
*/
|
||||
public boolean isFlushOnQuery() {
|
||||
return flushOnQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
|
||||
*/
|
||||
public TxScope setFlushOnQuery(boolean flushOnQuery) {
|
||||
this.flushOnQuery = flushOnQuery;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the isolation level.
|
||||
*/
|
||||
public int getIsolationLevel() {
|
||||
return isolation != null ? isolation.getLevel() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Isolation level this transaction should run with.
|
||||
*/
|
||||
|
||||
@@ -148,6 +148,12 @@ public interface UpdateQuery<T> {
|
||||
*/
|
||||
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
|
||||
|
||||
/**
|
||||
* Set the profile location of this update query. This is used to relate query execution metrics
|
||||
* back to a location like a specific line of code.
|
||||
*/
|
||||
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Return the query expression list to add predicates to.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Lookup internal services.
|
||||
*/
|
||||
class XServiceProvider {
|
||||
|
||||
private static SpiRawSqlService rawSqlService = initRawSql();
|
||||
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiRawSqlService?");
|
||||
}
|
||||
|
||||
private static SpiProfileLocationFactory initProfileLocation() {
|
||||
|
||||
Iterator<SpiProfileLocationFactory> loader = ServiceLoader.load(SpiProfileLocationFactory.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiProfileLocationFactory?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSqlService implementation.
|
||||
*/
|
||||
static SpiRawSqlService rawSql() {
|
||||
return rawSqlService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSqlService implementation.
|
||||
*/
|
||||
static SpiProfileLocationFactory profileLocationFactory() {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -226,4 +226,9 @@ public interface BeanCollection<E> extends Serializable {
|
||||
* additions and removals have been processed.
|
||||
*/
|
||||
void modifyReset();
|
||||
|
||||
/**
|
||||
* Return a shallow copy of this collection that is modifiable.
|
||||
*/
|
||||
BeanCollection<E> getShallowCopy();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.bean;
|
||||
|
||||
/**
|
||||
* Visitor for collecting new/old values for a bean update.
|
||||
*/
|
||||
public interface BeanDiffVisitor {
|
||||
|
||||
/**
|
||||
* Collect a new/old value pair.
|
||||
*/
|
||||
void visit(int position, Object newVal, Object oldVal);
|
||||
|
||||
/**
|
||||
* Start processing an associated bean.
|
||||
*/
|
||||
void visitPush(int position);
|
||||
|
||||
/**
|
||||
* Stop processing an associated bean.
|
||||
*/
|
||||
void visitPop();
|
||||
}
|
||||
@@ -26,14 +26,16 @@ public final class CallStack implements Serializable {
|
||||
|
||||
private final StackTraceElement[] callStack;
|
||||
|
||||
private final int hc;
|
||||
|
||||
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
|
||||
this.callStack = callStack;
|
||||
this.zeroHash = enc(zeroHash);
|
||||
this.pathHash = enc(pathHash);
|
||||
this.hc = computeHashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
private int computeHashCode() {
|
||||
int hc = 0;
|
||||
for (StackTraceElement aCallStack : callStack) {
|
||||
hc = 92821 * hc + aCallStack.hashCode();
|
||||
@@ -41,6 +43,10 @@ public final class CallStack implements Serializable {
|
||||
return hc;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean.bean;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -14,8 +13,14 @@ import java.io.Serializable;
|
||||
*/
|
||||
public interface EntityBean extends Serializable {
|
||||
|
||||
/**
|
||||
* Return all the property names in defined order.
|
||||
*/
|
||||
String[] _ebean_getPropertyNames();
|
||||
|
||||
/**
|
||||
* Return the property name at the given position.
|
||||
*/
|
||||
String _ebean_getPropertyName(int pos);
|
||||
|
||||
/**
|
||||
@@ -32,16 +37,6 @@ public interface EntityBean extends Serializable {
|
||||
*/
|
||||
Object _ebean_newInstance();
|
||||
|
||||
/**
|
||||
* Add a PropertyChangeListener to this bean.
|
||||
*/
|
||||
void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Remove a PropertyChangeListener from this bean.
|
||||
*/
|
||||
void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Generated method that sets the loaded state on all the embedded beans on
|
||||
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
|
||||
|
||||
@@ -3,15 +3,12 @@ package io.ebean.bean;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.ValuePair;
|
||||
|
||||
import java.util.Arrays;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
@@ -34,8 +31,6 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
|
||||
private transient NodeUsageCollector nodeUsageCollector;
|
||||
|
||||
private transient PropertyChangeSupport pcs;
|
||||
|
||||
private transient PersistenceContext persistenceContext;
|
||||
|
||||
private transient BeanLoader beanLoader;
|
||||
@@ -128,46 +123,6 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.persistenceContext = persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property change listener for this entity bean.
|
||||
*/
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
if (pcs == null) {
|
||||
pcs = new PropertyChangeSupport(owner);
|
||||
}
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a property change listener for this entity bean for a specific
|
||||
* property.
|
||||
*/
|
||||
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
if (pcs == null) {
|
||||
pcs = new PropertyChangeSupport(owner);
|
||||
}
|
||||
pcs.addPropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property change listener for this entity bean.
|
||||
*/
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
if (pcs != null) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a property change listener for this entity bean for a specific
|
||||
* property.
|
||||
*/
|
||||
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
if (pcs != null) {
|
||||
pcs.removePropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on profile collection.
|
||||
*/
|
||||
@@ -507,6 +462,15 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all properties to be loaded (post insert).
|
||||
*/
|
||||
public void setLoadedPropertyAll() {
|
||||
for (int i = 0; i < loadedProps.length; i++) {
|
||||
loadedProps[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is loaded.
|
||||
*/
|
||||
@@ -694,6 +658,28 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively add dirty properties.
|
||||
*/
|
||||
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
|
||||
int len = getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (changedProps != null && changedProps[i]) {
|
||||
// the property has been changed on this bean
|
||||
Object newVal = owner._ebean_getField(i);
|
||||
Object oldVal = getOrigValue(i);
|
||||
visitor.visit(i, newVal, oldVal);
|
||||
|
||||
} else if (embeddedDirty != null && embeddedDirty[i]) {
|
||||
// an embedded property has been changed - recurse
|
||||
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
|
||||
visitor.visitPush(i);
|
||||
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
|
||||
visitor.visitPop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a dirty property hash taking into account embedded beans.
|
||||
*/
|
||||
@@ -860,9 +846,9 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
loadedProps[propertyIndex] = true;
|
||||
}
|
||||
|
||||
private void preGetterCallback() {
|
||||
private void preGetterCallback(int propertyIndex) {
|
||||
if (preGetterCallback != null) {
|
||||
preGetterCallback.preGetterTrigger();
|
||||
preGetterCallback.preGetterTrigger(propertyIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,14 +856,14 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Called prior to Id property getter.
|
||||
*/
|
||||
public void preGetId() {
|
||||
preGetterCallback();
|
||||
preGetterCallback(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that is called prior to a getter method on the actual entity.
|
||||
*/
|
||||
public void preGetter(int propertyIndex) {
|
||||
preGetterCallback();
|
||||
preGetterCallback(propertyIndex);
|
||||
if (state == STATE_NEW || disableLazyLoad) {
|
||||
return;
|
||||
}
|
||||
@@ -889,49 +875,17 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for "enhancement" postSetter processing. This is around a PUTFIELD
|
||||
* so no need to check the newValue afterwards.
|
||||
*/
|
||||
public void postSetter(PropertyChangeEvent event) {
|
||||
if (pcs != null && event != null) {
|
||||
pcs.firePropertyChange(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called for "subclassed" postSetter processing. Here the newValue has to be
|
||||
* re-fetched (and passed into this method) in case there is code inside the
|
||||
* setter that further mutates the value.
|
||||
*/
|
||||
public void postSetter(PropertyChangeEvent event, Object newValue) {
|
||||
if (pcs != null && event != null) {
|
||||
if (newValue != null && newValue.equals(event.getNewValue())) {
|
||||
pcs.firePropertyChange(event);
|
||||
} else {
|
||||
pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OneToMany and ManyToMany don't have any interception so just check for
|
||||
* PropertyChangeSupport.
|
||||
*/
|
||||
public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
|
||||
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
|
||||
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("This bean is readOnly");
|
||||
}
|
||||
|
||||
setLoadedProperty(propertyIndex);
|
||||
|
||||
// Bean itself not considered dirty when many changed
|
||||
if (pcs != null) {
|
||||
return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
|
||||
@@ -960,168 +914,140 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
* Check to see if the values are not equal. If they are not equal then create
|
||||
* the old values for use with ConcurrencyMode.ALL.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!areEqual(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for primitive boolean.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for primitive int.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* long.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* double.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (Double.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* float.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (Float.compare(oldValue, newValue) != 0) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* short.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* char.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte.
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (oldValue != newValue) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* char[].
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!Arrays.equals(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* byte[].
|
||||
*/
|
||||
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
|
||||
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
|
||||
|
||||
if (state == STATE_NEW) {
|
||||
setLoadedProperty(propertyIndex);
|
||||
} else if (!Arrays.equals(oldValue, newValue)) {
|
||||
setChangedPropertyValue(propertyIndex, intercept, oldValue);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set an old value.
|
||||
*/
|
||||
public void setOldValue(int propertyIndex, Object oldValue) {
|
||||
setChangedPropertyValue(propertyIndex, true, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ package io.ebean.bean;
|
||||
public interface PreGetterCallback {
|
||||
|
||||
/**
|
||||
* Trigger the callback.
|
||||
* Trigger the callback based on a getter on a property.
|
||||
*
|
||||
* @param propertyIndex The index of the property
|
||||
*/
|
||||
void preGetterTrigger();
|
||||
void preGetterTrigger(int propertyIndex);
|
||||
}
|
||||
|
||||
+32
-2
@@ -1,5 +1,9 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents part of the "L2" server side cache.
|
||||
* <p>
|
||||
@@ -13,20 +17,46 @@ package io.ebean.cache;
|
||||
*/
|
||||
public interface ServerCache {
|
||||
|
||||
default Map<Object, Object> getAll(Set<Object> keys) {
|
||||
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
for (Object key : keys) {
|
||||
Object value = get(key);
|
||||
if (value != null) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value given the key.
|
||||
*/
|
||||
Object get(Object id);
|
||||
|
||||
/**
|
||||
* Put all the values in the cache.
|
||||
*/
|
||||
default void putAll(Map<Object, Object> keyValues) {
|
||||
keyValues.forEach(this::put);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the value in the cache with a given id.
|
||||
*/
|
||||
Object put(Object id, Object value);
|
||||
void put(Object id, Object value);
|
||||
|
||||
/**
|
||||
* Remove the entries from the cache given the id values.
|
||||
*/
|
||||
default void removeAll(Set<Object> keys) {
|
||||
keys.forEach(this::remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a entry from the cache given its id.
|
||||
*/
|
||||
Object remove(Object id);
|
||||
void remove(Object id);
|
||||
|
||||
/**
|
||||
* Clear all entries from the cache.
|
||||
|
||||
@@ -44,4 +44,19 @@ public interface ServerCacheManager {
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* Clear all the local caches.
|
||||
*
|
||||
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
|
||||
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
|
||||
*/
|
||||
void clearAllLocal();
|
||||
|
||||
/**
|
||||
* Clear the local caches for this bean type.
|
||||
*
|
||||
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
|
||||
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
|
||||
*/
|
||||
void clearLocal(Class<?> beanType);
|
||||
}
|
||||
|
||||
@@ -213,4 +213,16 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
boolean holdsModifications() {
|
||||
return modifyHolder != null && modifyHolder.hasModifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
|
||||
* @param other
|
||||
*/
|
||||
protected void setFromOriginal(AbstractBeanCollection<E> other) {
|
||||
this.disableLazyLoad = other.disableLazyLoad;
|
||||
this.ebeanServerName = other.ebeanServerName;
|
||||
this.loader = other.loader;
|
||||
this.ownerBean = other.ownerBean;
|
||||
this.propertyName = other.propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
throw new IllegalStateException("This collection is in ReadOnly mode");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BeanCollection<E> getShallowCopy() {
|
||||
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
|
||||
copy.setFromOriginal(this);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
|
||||
* to modify lists from cache.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* The underlying List implementation.
|
||||
*/
|
||||
private List<E> list;
|
||||
|
||||
|
||||
public CopyOnFirstWriteList(List<E> list) {
|
||||
super();
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
private volatile boolean copied = false;
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
checkCopyOnWrite();
|
||||
return list.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends E> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
checkCopyOnWrite();
|
||||
return list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<E> operator) {
|
||||
checkCopyOnWrite();
|
||||
list.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super E> filter) {
|
||||
checkCopyOnWrite();
|
||||
return list.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super E> c) {
|
||||
checkCopyOnWrite();
|
||||
list.sort(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>();
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return list.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E set(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
return list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, E element) {
|
||||
checkCopyOnWrite();
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove(int index) {
|
||||
checkCopyOnWrite();
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
private void checkCopyOnWrite() {
|
||||
if (!copied) {
|
||||
synchronized (this) {
|
||||
if (!copied) {
|
||||
list = new ArrayList<>(list);
|
||||
copied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.Table;
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
/**
|
||||
* Provides some base implementation for NamingConventions.
|
||||
@@ -219,15 +219,10 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
* Return true if this class is part of entity inheritance.
|
||||
*/
|
||||
protected boolean hasInheritance(Class<?> supCls) {
|
||||
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
|
||||
return AnnotationUtil.findAnnotationRecursive(supCls, Inheritance.class) != null
|
||||
|| AnnotationUtil.findAnnotation(supCls, DiscriminatorValue.class) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the class has the given annotation.
|
||||
*/
|
||||
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
|
||||
return supCls.getAnnotation(annotation) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
|
||||
@@ -258,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
*/
|
||||
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
|
||||
|
||||
final Table t = findTableAnnotation(beanClass);
|
||||
final Table t = AnnotationUtil.findAnnotationRecursive(beanClass, Table.class);
|
||||
|
||||
// Take the annotation if defined
|
||||
if (t != null && !isEmpty(t.name())) {
|
||||
@@ -271,22 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search recursively for an @Table in the class hierarchy.
|
||||
*/
|
||||
protected Table findTableAnnotation(Class<?> cls) {
|
||||
while (true) {
|
||||
if (cls.equals(Object.class)) {
|
||||
return null;
|
||||
}
|
||||
Table table = cls.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace back ticks (if they are used) with database platform specific
|
||||
* quoted identifiers.
|
||||
|
||||
@@ -32,6 +32,15 @@ public class AutoTuneConfig {
|
||||
public AutoTuneConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we are profiling or query tuning.
|
||||
*
|
||||
* If we are not doing either then we don't need a CallStack.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return profiling || queryTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the file that holds the query tuning information.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,22 @@ public class ClassLoadConfig {
|
||||
return isPresent("javax.validation.constraints.NotNull");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax PostConstruct annotation is present (maybe not in java9).
|
||||
* If not we don't support PostConstruct lifecycle events.
|
||||
*/
|
||||
public boolean isJavaxPostConstructPresent() {
|
||||
return isPresent("javax.annotation.PostConstruct");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if javax JAXB is present (maybe not in java9).
|
||||
* If not we don't try to parse or support 'extra ddl'.
|
||||
*/
|
||||
public boolean isJavaxJAXBPresent() {
|
||||
return isPresent("javax.xml.bind.JAXBException");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Jackson annotations like JsonIgnore are present.
|
||||
*/
|
||||
|
||||
@@ -10,22 +10,82 @@ import java.util.Properties;
|
||||
*/
|
||||
public class ContainerConfig {
|
||||
|
||||
protected boolean clusterActive;
|
||||
private boolean active;
|
||||
private String serviceName;
|
||||
private String namespace;
|
||||
private String podName;
|
||||
private int port;
|
||||
|
||||
protected Properties properties;
|
||||
private Properties properties;
|
||||
|
||||
/**
|
||||
* Return the service name.
|
||||
*/
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the service name.
|
||||
*/
|
||||
public void setServiceName(String serviceName) {
|
||||
this.serviceName = serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the namespace.
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the namespace.
|
||||
*/
|
||||
public void setNamespace(String namespace) {
|
||||
this.namespace = namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the pod name.
|
||||
*/
|
||||
public String getPodName() {
|
||||
return podName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pod name.
|
||||
*/
|
||||
public void setPodName(String podName) {
|
||||
this.podName = podName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port to use.
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port to use.
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if clustering is active.
|
||||
*/
|
||||
public boolean isClusterActive() {
|
||||
return clusterActive;
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true for clustering to be active.
|
||||
*/
|
||||
public void setClusterActive(boolean clusterActive) {
|
||||
this.clusterActive = clusterActive;
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +107,14 @@ public class ContainerConfig {
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
this.properties = properties;
|
||||
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
|
||||
this.active = getProperty(properties, "ebean.cluster.active", active);
|
||||
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
|
||||
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
|
||||
this.podName = properties.getProperty("ebean.cluster.podName", podName);
|
||||
String portParam = properties.getProperty("ebean.cluster.port");
|
||||
if (portParam != null) {
|
||||
this.port = Integer.parseInt(portParam);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
/**
|
||||
* Custom mappings for DB types that override the default.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.dbmigration.MigrationConfig;
|
||||
import io.ebean.dbmigration.MigrationRunner;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.migration.MigrationConfig;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,6 +16,8 @@ public class DbMigrationConfig {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
|
||||
|
||||
protected MigrationConfig runnerConfig = new MigrationConfig();
|
||||
|
||||
/**
|
||||
* The database platform to generate migration DDL for.
|
||||
*/
|
||||
@@ -105,6 +107,15 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
protected String dbPassword;
|
||||
|
||||
protected String patchInsertOn;
|
||||
|
||||
protected String patchResetChecksumOn;
|
||||
|
||||
/**
|
||||
* Mode used to check non-null columns added via migration have a default value specified etc.
|
||||
*/
|
||||
protected boolean strictMode = true;
|
||||
|
||||
/**
|
||||
* Return the DB platform to generate migration DDL for.
|
||||
* <p>
|
||||
@@ -352,6 +363,65 @@ public class DbMigrationConfig {
|
||||
this.dbPassword = dbPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should be added to history without running.
|
||||
*/
|
||||
public String getPatchInsertOn() {
|
||||
return patchInsertOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should be added to history without running.
|
||||
* <p>
|
||||
* Value can be a string containing comma delimited list of version numbers.
|
||||
* </p>
|
||||
*/
|
||||
public void setPatchInsertOn(String patchInsertOn) {
|
||||
this.patchInsertOn = patchInsertOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return migration versions that should have their checksum reset and not run.
|
||||
*/
|
||||
public String getPatchResetChecksumOn() {
|
||||
return patchResetChecksumOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration versions that should have their checksum reset and not run.
|
||||
* <p>
|
||||
* Value can be a string containing comma delimited list of version numbers.
|
||||
* </p>
|
||||
*/
|
||||
public void setPatchResetChecksumOn(String patchResetChecksumOn) {
|
||||
this.patchResetChecksumOn = patchResetChecksumOn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if strict mode is used which includes a check that non-null columns have a default value.
|
||||
*/
|
||||
public boolean isStrictMode() {
|
||||
String envValue = readEnvironment("ddl.migration.strictMode");
|
||||
if (!isEmpty(envValue)) {
|
||||
return Boolean.parseBoolean(envValue.trim());
|
||||
}
|
||||
return strictMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to false to turn off strict mode allowing non-null columns to not have a default value.
|
||||
*/
|
||||
public void setStrictMode(boolean strictMode) {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying migration runner configuration allowing for more advanced settings.
|
||||
*/
|
||||
public MigrationConfig getRunnerConfig() {
|
||||
return runnerConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the settings from the PropertiesWrapper.
|
||||
*/
|
||||
@@ -370,6 +440,8 @@ public class DbMigrationConfig {
|
||||
generate = properties.getBoolean("migration.generate", generate);
|
||||
version = properties.get("migration.version", version);
|
||||
name = properties.get("migration.name", name);
|
||||
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
|
||||
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
|
||||
|
||||
runMigration = properties.getBoolean("migration.run", runMigration);
|
||||
metaTable = properties.get("migration.metaTable", metaTable);
|
||||
@@ -446,7 +518,6 @@ public class DbMigrationConfig {
|
||||
*/
|
||||
public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) {
|
||||
|
||||
MigrationConfig runnerConfig = new MigrationConfig();
|
||||
runnerConfig.setMetaTable(metaTable);
|
||||
runnerConfig.setApplySuffix(applySuffix);
|
||||
runnerConfig.setMigrationPath(migrationPath);
|
||||
@@ -455,6 +526,12 @@ public class DbMigrationConfig {
|
||||
runnerConfig.setDbUsername(getDbUsername());
|
||||
runnerConfig.setDbPassword(getDbPassword());
|
||||
runnerConfig.setClassLoader(classLoader);
|
||||
if (patchInsertOn != null) {
|
||||
runnerConfig.setPatchInsertOn(patchInsertOn);
|
||||
}
|
||||
if (patchResetChecksumOn != null) {
|
||||
runnerConfig.setPatchResetChecksumOn(patchResetChecksumOn);
|
||||
}
|
||||
if (properties != null) {
|
||||
runnerConfig.load(properties);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.ebean.config;
|
||||
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -12,7 +12,7 @@ public interface EncryptKeyManager {
|
||||
* This gives the EncryptKeyManager the opportunity to get keys etc.
|
||||
* </p>
|
||||
*/
|
||||
default void initialise() {};
|
||||
default void initialise() {}
|
||||
|
||||
/**
|
||||
* Return the key used to encrypt and decrypt a property mapping to the given
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Configuration for transaction profiling.
|
||||
*/
|
||||
public class ProfilingConfig {
|
||||
|
||||
/**
|
||||
* When true transaction profiling is enabled.
|
||||
*/
|
||||
private boolean enabled;
|
||||
|
||||
/**
|
||||
* Set true for verbose mode.
|
||||
*/
|
||||
private boolean verbose;
|
||||
|
||||
/**
|
||||
* The minimum transaction execution time to be included in profiling.
|
||||
*/
|
||||
private long minimumMicros;
|
||||
|
||||
/**
|
||||
* A specific set of profileIds to include in profiling.
|
||||
*/
|
||||
private int[] includeProfileIds = {};
|
||||
|
||||
/**
|
||||
* The number of profiles to write per file.
|
||||
*/
|
||||
private long profilesPerFile = 1000;
|
||||
|
||||
private String directory = "profiling";
|
||||
|
||||
/**
|
||||
* Return true if transaction profiling is enabled.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable transaction profiling.
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if verbose mode is used.
|
||||
*/
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to use verbose mode.
|
||||
*/
|
||||
public void setVerbose(boolean verbose) {
|
||||
this.verbose = verbose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the minimum transaction execution to be included in profiling.
|
||||
*/
|
||||
public long getMinimumMicros() {
|
||||
return minimumMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum transaction execution to be included in profiling.
|
||||
*/
|
||||
public void setMinimumMicros(long minimumMicros) {
|
||||
this.minimumMicros = minimumMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the specific set of profileIds to include in profiling.
|
||||
* When not set all transactions with profileIds are included.
|
||||
*/
|
||||
public int[] getIncludeProfileIds() {
|
||||
return includeProfileIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a specific set of profileIds to include in profiling.
|
||||
* When not set all transactions with profileIds are included.
|
||||
*/
|
||||
public void setIncludeProfileIds(int[] includeProfileIds) {
|
||||
this.includeProfileIds = includeProfileIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of profiles to write to a single file.
|
||||
*/
|
||||
public long getProfilesPerFile() {
|
||||
return profilesPerFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of profiles to write to a single file.
|
||||
*/
|
||||
public void setProfilesPerFile(long profilesPerFile) {
|
||||
this.profilesPerFile = profilesPerFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory profiling files are put into.
|
||||
*/
|
||||
public String getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the directory profiling files are put into.
|
||||
*/
|
||||
public void setDirectory(String directory) {
|
||||
this.directory = directory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load setting from properties.
|
||||
*/
|
||||
public void loadSettings(PropertiesWrapper p, String name) {
|
||||
|
||||
enabled = p.getBoolean("profiling", enabled);
|
||||
verbose = p.getBoolean("profiling.verbose", verbose);
|
||||
|
||||
directory = p.get("profiling.directory", directory);
|
||||
profilesPerFile = p.getLong("profiling.profilesPerFile", profilesPerFile);
|
||||
minimumMicros = p.getLong("profiling.minimumMicros", minimumMicros);
|
||||
|
||||
String includeIds = p.get("profiling.includeProfileIds");
|
||||
if (includeIds != null) {
|
||||
includeProfileIds = parseIds(includeIds);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] parseIds(String includeIds) {
|
||||
|
||||
String[] ids = includeIds.split(",");
|
||||
int[] vals = new int[ids.length];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
vals[i] = Integer.parseInt(ids[i]);
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,16 @@ package io.ebean.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import io.ebean.EbeanServerFactory;
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.Encrypted;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.dbmigration.DbOffline;
|
||||
import io.ebean.dbmigration.MigrationRunner;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
@@ -28,11 +26,13 @@ import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import java.util.regex.Pattern;
|
||||
import io.ebean.migration.MigrationRunner;
|
||||
import io.ebean.util.StringHelper;
|
||||
import org.avaje.datasource.DataSourceConfig;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -77,8 +77,6 @@ import java.util.ServiceLoader;
|
||||
*/
|
||||
public class ServerConfig {
|
||||
|
||||
private static final Pattern NAMES_SPLIT = Pattern.compile("[ ,;]");
|
||||
|
||||
/**
|
||||
* The EbeanServer name.
|
||||
*/
|
||||
@@ -275,11 +273,29 @@ public class ServerConfig {
|
||||
*/
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* The read only data source (can be null).
|
||||
*/
|
||||
private DataSource readOnlyDataSource;
|
||||
|
||||
/**
|
||||
* The data source config.
|
||||
*/
|
||||
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
|
||||
* I believe this will default to true in some future release (as it has a nice performance benefit).
|
||||
*
|
||||
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
|
||||
*/
|
||||
private boolean autoReadOnlyDataSource;
|
||||
|
||||
/**
|
||||
* Optional configuration for a read only data source.
|
||||
*/
|
||||
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
|
||||
|
||||
/**
|
||||
* The db migration config (migration resource path etc).
|
||||
*/
|
||||
@@ -370,6 +386,8 @@ public class ServerConfig {
|
||||
|
||||
private ChangeLogRegister changeLogRegister;
|
||||
|
||||
private boolean changeLogAsync = true;
|
||||
|
||||
private ReadAuditLogger readAuditLogger;
|
||||
|
||||
private ReadAuditPrepare readAuditPrepare;
|
||||
@@ -435,11 +453,29 @@ public class ServerConfig {
|
||||
|
||||
private String jodaLocalTimeMode;
|
||||
|
||||
/**
|
||||
* Time to live for query plans - defaults to 5 minutes.
|
||||
*/
|
||||
private int queryPlanTTLSeconds = 60 * 5;
|
||||
|
||||
/**
|
||||
* Set to true to globally disable L2 caching (typically for performance testing).
|
||||
*/
|
||||
private boolean disableL2Cache;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
private long slowQueryMillis;
|
||||
|
||||
/**
|
||||
* The listener for processing slow query events.
|
||||
*/
|
||||
private SlowQueryListener slowQueryListener;
|
||||
|
||||
|
||||
private ProfilingConfig profilingConfig = new ProfilingConfig();
|
||||
|
||||
/**
|
||||
* Construct a Server Configuration for programmatically creating an EbeanServer.
|
||||
*/
|
||||
@@ -447,6 +483,34 @@ public class ServerConfig {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query time in millis.
|
||||
*/
|
||||
public long getSlowQueryMillis() {
|
||||
return slowQueryMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the slow query time in millis.
|
||||
*/
|
||||
public void setSlowQueryMillis(long slowQueryMillis) {
|
||||
this.slowQueryMillis = slowQueryMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slow query event listener.
|
||||
*/
|
||||
public SlowQueryListener getSlowQueryListener() {
|
||||
return slowQueryListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the slow query event listener.
|
||||
*/
|
||||
public void setSlowQueryListener(SlowQueryListener slowQueryListener) {
|
||||
this.slowQueryListener = slowQueryListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a service object into configuration such that it can be passed to a plugin.
|
||||
* <p>
|
||||
@@ -934,6 +998,20 @@ public class ServerConfig {
|
||||
this.changeLogIncludeInserts = changeLogIncludeInserts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true (default) if the changelog should be written async.
|
||||
*/
|
||||
public boolean isChangeLogAsync() {
|
||||
return changeLogAsync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the changelog should be written async (default = true).
|
||||
*/
|
||||
public void setChangeLogAsync(boolean changeLogAsync) {
|
||||
this.changeLogAsync = changeLogAsync;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ReadAuditLogger to use.
|
||||
*/
|
||||
@@ -969,6 +1047,20 @@ public class ServerConfig {
|
||||
this.readAuditPrepare = readAuditPrepare;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for profiling.
|
||||
*/
|
||||
public ProfilingConfig getProfilingConfig() {
|
||||
return profilingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for profiling.
|
||||
*/
|
||||
public void setProfilingConfig(ProfilingConfig profilingConfig) {
|
||||
this.profilingConfig = profilingConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB migration configuration.
|
||||
*/
|
||||
@@ -1368,6 +1460,28 @@ public class ServerConfig {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the read only DataSource.
|
||||
*/
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read only DataSource.
|
||||
* <p>
|
||||
* Note that the DataSource is expected to use AutoCommit true mode avoiding the need
|
||||
* for explicit commit (or rollback).
|
||||
* </p>
|
||||
* <p>
|
||||
* This read only DataSource will be used for implicit query only transactions. It is not
|
||||
* used if the transaction is created explicitly or if the query is an update or delete query.
|
||||
* </p>
|
||||
*/
|
||||
public void setReadOnlyDataSource(DataSource readOnlyDataSource) {
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration to build a DataSource using Ebean's own DataSource
|
||||
* implementation.
|
||||
@@ -1384,6 +1498,42 @@ public class ServerConfig {
|
||||
this.dataSourceConfig = dataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Ebean should create a DataSource for use with implicit read only transactions.
|
||||
*/
|
||||
public boolean isAutoReadOnlyDataSource() {
|
||||
return autoReadOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Ebean should create a DataSource for use with implicit read only transactions.
|
||||
*/
|
||||
public void setAutoReadOnlyDataSource(boolean autoReadOnlyDataSource) {
|
||||
this.autoReadOnlyDataSource = autoReadOnlyDataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the configuration for the read only DataSource.
|
||||
* <p>
|
||||
* This is only used if autoReadOnlyDataSource is true.
|
||||
* </p>
|
||||
* <p>
|
||||
* The driver, url, username and password default to the configuration for the main DataSource if they are not
|
||||
* set on this configuration. This means there is actually no need to set any configuration here and we only
|
||||
* set configuration for url, username and password etc if it is different from the main DataSource.
|
||||
* </p>
|
||||
*/
|
||||
public DataSourceConfig getReadOnlyDataSourceConfig() {
|
||||
return readOnlyDataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configuration for the read only DataSource.
|
||||
*/
|
||||
public void setReadOnlyDataSourceConfig(DataSourceConfig readOnlyDataSourceConfig) {
|
||||
this.readOnlyDataSourceConfig = readOnlyDataSourceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JNDI name of the DataSource to use.
|
||||
*/
|
||||
@@ -1633,16 +1783,6 @@ public class ServerConfig {
|
||||
this.encryptor = encryptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the instance is coming up in offline mode.
|
||||
* <p>
|
||||
* Offline mode is mostly used when generating DB migration.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isOfflineMode() {
|
||||
return dbOffline || DbOffline.isSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the EbeanServer instance should be created in offline mode.
|
||||
*/
|
||||
@@ -2504,6 +2644,7 @@ public class ServerConfig {
|
||||
*/
|
||||
protected void loadSettings(PropertiesWrapper p) {
|
||||
|
||||
profilingConfig.loadSettings(p, name);
|
||||
migrationConfig.loadSettings(p, name);
|
||||
|
||||
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
|
||||
@@ -2535,11 +2676,14 @@ public class ServerConfig {
|
||||
dbTypeConfig.setGeometrySRID(srid);
|
||||
}
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
|
||||
|
||||
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
|
||||
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
|
||||
@@ -2580,6 +2724,7 @@ public class ServerConfig {
|
||||
|
||||
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope", "TRANSACTION"));
|
||||
|
||||
changeLogAsync = p.getBoolean("changeLogAsync", changeLogAsync);
|
||||
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
|
||||
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
|
||||
expressionNativeIlike = p.getBoolean("expressionNativeIlike", expressionNativeIlike);
|
||||
@@ -2647,10 +2792,9 @@ public class ServerConfig {
|
||||
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
|
||||
String[] split = NAMES_SPLIT.split(classNames);
|
||||
for (String aSplit : split) {
|
||||
String cn = aSplit.trim();
|
||||
if (!cn.isEmpty() && !"class".equalsIgnoreCase(cn)) {
|
||||
String[] split = StringHelper.splitNames(classNames);
|
||||
for (String cn : split) {
|
||||
if (!"class".equalsIgnoreCase(cn)) {
|
||||
try {
|
||||
classes.add(Class.forName(cn));
|
||||
} catch (ClassNotFoundException e) {
|
||||
@@ -2664,16 +2808,16 @@ public class ServerConfig {
|
||||
|
||||
private List<String> getSearchJarsPackages(String searchPackages) {
|
||||
|
||||
List<String> hitList = new ArrayList<>();
|
||||
|
||||
if (searchPackages != null) {
|
||||
String[] entries = StringHelper.splitNames(searchPackages);
|
||||
|
||||
String[] entries = NAMES_SPLIT.split(searchPackages);
|
||||
for (String entry : entries) {
|
||||
hitList.add(entry.trim());
|
||||
}
|
||||
List<String> hitList = new ArrayList<>(entries.length);
|
||||
Collections.addAll(hitList, entries);
|
||||
|
||||
return hitList;
|
||||
} else {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return hitList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2756,6 +2900,20 @@ public class ServerConfig {
|
||||
this.disableL2Cache = disableL2Cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan time to live.
|
||||
*/
|
||||
public int getQueryPlanTTLSeconds() {
|
||||
return queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query plan time to live.
|
||||
*/
|
||||
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
|
||||
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the DB migration against the DataSource.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
|
||||
/**
|
||||
* Slow query event.
|
||||
*/
|
||||
public class SlowQueryEvent {
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final long timeMillis;
|
||||
|
||||
private final int rowCount;
|
||||
|
||||
private final ObjectGraphNode originNode;
|
||||
|
||||
/**
|
||||
* Construct with the SQL and execution time in millis.
|
||||
*/
|
||||
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
|
||||
this.sql = sql;
|
||||
this.timeMillis = timeMillis;
|
||||
this.rowCount = rowCount;
|
||||
this.originNode = originNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL for the slow query.
|
||||
*/
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the execution time in millis.
|
||||
*/
|
||||
public long getTimeMillis() {
|
||||
return timeMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total row count associated with the query.
|
||||
*/
|
||||
public int getRowCount() {
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin point for the root query.
|
||||
* <p>
|
||||
* Typically the <code>originNode.getOriginQueryPoint().getFirstStackElement()</code> provides the stack line that
|
||||
* shows the code that invoked the query.
|
||||
* </p>
|
||||
*/
|
||||
public ObjectGraphNode getOriginNode() {
|
||||
return originNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Listener for slow query events.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface SlowQueryListener {
|
||||
|
||||
/**
|
||||
* Process a slow query event.
|
||||
*/
|
||||
void process(SlowQueryEvent event);
|
||||
}
|
||||
@@ -16,5 +16,5 @@ public interface TenantDataSourceProvider {
|
||||
/**
|
||||
* Shutdown all the DataSources.
|
||||
*/
|
||||
default void shutdown(boolean deregisterDriver) {};
|
||||
default void shutdown(boolean deregisterDriver) {}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
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.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -64,6 +61,8 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
protected String concatOperator = "||";
|
||||
|
||||
/**
|
||||
* When set to true all db column names and table names use quoted identifiers.
|
||||
*/
|
||||
@@ -141,9 +140,25 @@ public class DatabasePlatform {
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
* The non-escaped like clause (to stop slash being escaped on some platforms).
|
||||
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
protected String likeClauseRaw = "like ? escape''";
|
||||
|
||||
/**
|
||||
* Escaped like clause for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected String likeClauseEscaped = "like ? escape'|'";
|
||||
|
||||
/**
|
||||
* Escape character used for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char likeEscapeChar = '|';
|
||||
|
||||
/**
|
||||
* Characters escaped for startsWith, endsWith and contains.
|
||||
*/
|
||||
protected char[] likeSpecialCharacters = { '%', '_', '|' };
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
@@ -162,8 +177,6 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
|
||||
|
||||
protected PlatformDdl platformDdl;
|
||||
|
||||
/**
|
||||
* The maximum length of table names - used specifically when derived
|
||||
* default table names for intersection tables.
|
||||
@@ -180,8 +193,6 @@ public class DatabasePlatform {
|
||||
|
||||
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
|
||||
|
||||
protected char[] specialLikeCharacters = { '%', '_' };
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
@@ -232,7 +243,7 @@ public class DatabasePlatform {
|
||||
* Return true if this matches the given platform.
|
||||
*/
|
||||
public boolean isPlatform(Platform platform) {
|
||||
return this.platform.equals(platform);
|
||||
return this.platform == platform;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,20 +287,6 @@ public class DatabasePlatform {
|
||||
return maxConstraintNameLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the platform specific DDL.
|
||||
*/
|
||||
public PlatformDdl getPlatformDdl() {
|
||||
return platformDdl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a DDL handler for generating DDL scripts.
|
||||
*/
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the JDBC driver does not allow additional queries to execute
|
||||
* when a resultSet is being 'streamed' as is the case with findEach() etc.
|
||||
@@ -409,8 +406,6 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
@@ -418,17 +413,20 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB concat operator.
|
||||
*/
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
@@ -599,8 +597,8 @@ public class DatabasePlatform {
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
public String getLikeClause(boolean rawLikeExpression) {
|
||||
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,7 +641,7 @@ public class DatabasePlatform {
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
boolean escaped = false;
|
||||
for (char escapeChar: specialLikeCharacters) {
|
||||
for (char escapeChar: likeSpecialCharacters) {
|
||||
if (ch == escapeChar) {
|
||||
if (sb == null) {
|
||||
sb = new StringBuilder(value.substring(0, i));
|
||||
@@ -665,6 +663,6 @@ public class DatabasePlatform {
|
||||
}
|
||||
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('\\').append(ch);
|
||||
sb.append(likeEscapeChar).append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import io.ebean.annotation.DbDefault;
|
||||
|
||||
/**
|
||||
* DB Column default values mapping to database platform specific literals.
|
||||
*/
|
||||
@@ -22,6 +27,13 @@ public class DbDefaultValue {
|
||||
* The key for the NOW / current timestamp.
|
||||
*/
|
||||
public static final String NOW = "now";
|
||||
|
||||
/**
|
||||
* The 'null' literal.
|
||||
*/
|
||||
public static final String NULL = "null";
|
||||
|
||||
|
||||
|
||||
protected Map<String, String> map = new LinkedHashMap<>();
|
||||
|
||||
@@ -63,8 +75,128 @@ public class DbDefaultValue {
|
||||
if (dbDefaultLiteral == null) {
|
||||
return null;
|
||||
}
|
||||
if (dbDefaultLiteral.startsWith("$RAW:")) {
|
||||
return dbDefaultLiteral.substring(5);
|
||||
}
|
||||
String val = map.get(dbDefaultLiteral);
|
||||
return val != null ? val : dbDefaultLiteral;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
|
||||
*
|
||||
* This is mainly to quote string literals and verify integer/dates for correctness.
|
||||
* <p>
|
||||
* Note: There are some special cases:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
|
||||
* returns: <code>default 'User''s default'</code><br/>
|
||||
* (the same on an integer property will throw a NumberFormatException)</li>
|
||||
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
|
||||
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
|
||||
* which gives you the <code>default 'null'</code> statement.</li>
|
||||
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
|
||||
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
|
||||
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
|
||||
if (propertyType == null
|
||||
|| defaultValue == null
|
||||
|| NULL.equals(defaultValue)
|
||||
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|
||||
|| (defaultValue.startsWith("$RAW:"))) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
|
||||
return toBooleanLiteral(defaultValue);
|
||||
}
|
||||
|
||||
if (Number.class.isAssignableFrom(propertyType)
|
||||
|| Byte.TYPE.equals(propertyType)
|
||||
|| Short.TYPE.equals(propertyType)
|
||||
|| Integer.TYPE.equals(propertyType)
|
||||
|| Long.TYPE.equals(propertyType)
|
||||
|| Float.TYPE.equals(propertyType)
|
||||
|| Double.TYPE.equals(propertyType)
|
||||
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
|
||||
Double.valueOf(defaultValue); // verify if it is a number
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// check if it is a date/time - in all other cases return quoted defaultValue
|
||||
switch (sqlType) {
|
||||
// date
|
||||
case Types.DATE:
|
||||
return toDateLiteral(defaultValue);
|
||||
// time
|
||||
case Types.TIME:
|
||||
case Types.TIME_WITH_TIMEZONE:
|
||||
return toTimeLiteral(defaultValue);
|
||||
// timestamp
|
||||
case Types.TIMESTAMP:
|
||||
case Types.TIMESTAMP_WITH_TIMEZONE:
|
||||
return toDateTimeLiteral(defaultValue);
|
||||
|
||||
default:
|
||||
return toTextLiteral(defaultValue); // do not check other datatypes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
|
||||
*/
|
||||
private static String toBooleanLiteral(String value) {
|
||||
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
|
||||
return value;
|
||||
}
|
||||
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
|
||||
}
|
||||
|
||||
/**
|
||||
* This adds single qoutes around the <code>value</code> and doubles single quotes.
|
||||
* "User's home" will return "'User''s home'"
|
||||
*/
|
||||
private static String toTextLiteral(String value) {
|
||||
StringBuilder sb = new StringBuilder(value.length()+10);
|
||||
sb.append('\'');
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char ch = value.charAt(i);
|
||||
if (ch == '\'') {
|
||||
sb.append("''");
|
||||
} else {
|
||||
sb.append(ch);
|
||||
}
|
||||
}
|
||||
sb.append('\'');
|
||||
return sb.toString();
|
||||
|
||||
}
|
||||
|
||||
private static String toDateLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDate(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
|
||||
private static String toDateTimeLiteral(String value) {
|
||||
if (NOW.equals(value)) {
|
||||
return value; // this will get translated later
|
||||
}
|
||||
DatatypeConverter.parseDateTime(value); // verify
|
||||
return toTextLiteral(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import io.ebean.dbmigration.migration.IdentityType;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -121,28 +120,4 @@ public class DbIdentity {
|
||||
this.idType = idType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the id type to use based on requested identityType and
|
||||
* the support for that in the database platform.
|
||||
*/
|
||||
public IdType useIdentityType(IdentityType identityType) {
|
||||
|
||||
if (identityType == null) {
|
||||
// use the default
|
||||
return idType;
|
||||
}
|
||||
switch (identityType) {
|
||||
case GENERATOR:
|
||||
return IdType.GENERATOR;
|
||||
case EXTERNAL:
|
||||
return IdType.EXTERNAL;
|
||||
case SEQUENCE:
|
||||
return supportsSequence ? IdType.SEQUENCE : idType;
|
||||
case IDENTITY:
|
||||
return supportsIdentity ? IdType.IDENTITY : idType;
|
||||
default:
|
||||
return idType;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ public class SqlCodeTranslator implements SqlExceptionTranslator {
|
||||
public PersistenceException translate(String message, SQLException e) {
|
||||
|
||||
DataErrorType errorType = map.get(e.getSQLState());
|
||||
if (errorType == null) {
|
||||
// fall back to error code
|
||||
errorType = map.get(String.valueOf(e.getErrorCode()));
|
||||
}
|
||||
if (errorType != null) {
|
||||
switch (errorType) {
|
||||
case AcquireLock:
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package io.ebean.config.dbplatform.db2;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.DB2Ddl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -24,7 +23,6 @@ public class DB2Platform extends DatabasePlatform {
|
||||
this.maxTableNameLength = 18;
|
||||
this.maxConstraintNameLength = 18;
|
||||
this.sqlLimiter = new Db2SqlLimiter();
|
||||
this.platformDdl = new DB2Ddl(this);
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
@@ -44,10 +42,14 @@ public class DB2Platform extends DatabasePlatform {
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
|
||||
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
|
||||
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 15));
|
||||
|
||||
|
||||
persistBatchOnCascade = PersistBatch.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
/**
|
||||
* Return a DB2 specific sequence IdGenerator that supports batch fetching
|
||||
* sequence values.
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package io.ebean.config.dbplatform.h2;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.H2Ddl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -22,7 +21,6 @@ public class H2Platform extends DatabasePlatform {
|
||||
super();
|
||||
this.platform = Platform.H2;
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
this.platformDdl = new H2Ddl(this);
|
||||
this.historySupport = new H2HistorySupport();
|
||||
this.nativeUuidType = true;
|
||||
this.dbDefaultValue.setNow("now()");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.config.dbplatform.hsqldb;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
@@ -9,7 +9,6 @@ import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.h2.H2DbEncrypt;
|
||||
import io.ebean.config.dbplatform.h2.H2SequenceIdGenerator;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.HsqldbDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@@ -22,7 +21,6 @@ public class HsqldbPlatform extends DatabasePlatform {
|
||||
super();
|
||||
this.platform = Platform.HSQLDB;
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
this.platformDdl = new HsqldbDdl(this);
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package io.ebean.config.dbplatform.mysql;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.MySqlDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -32,7 +31,6 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
this.useExtraTransactionOnIterateSecondaryQueries = true;
|
||||
this.selectCountWithAlias = true;
|
||||
this.dbEncrypt = new MySqlDbEncrypt();
|
||||
this.platformDdl = new MySqlDdl(this);
|
||||
this.historySupport = new MySqlHistorySupport();
|
||||
this.columnAliasPrefix = null;
|
||||
|
||||
@@ -44,12 +42,15 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
.addAcquireLock("1205")
|
||||
.addDuplicateKey("1062")
|
||||
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557","23000")
|
||||
.addDuplicateKey("1062", "1169")
|
||||
.addDataIntegrity("630", "839", "840", "893", "1215", "1216", "1217", "1364", "1451", "1452", "1557")
|
||||
.build();
|
||||
|
||||
this.openQuote = "`";
|
||||
this.closeQuote = "`";
|
||||
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
|
||||
this.likeClauseRaw = "like binary ? escape ''";
|
||||
this.likeClauseEscaped = "like binary ? escape '|'";
|
||||
|
||||
this.forwardOnlyHintOnFindIterate = true;
|
||||
this.booleanDbType = Types.BIT;
|
||||
@@ -78,4 +79,9 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
// NOWAIT and SKIP LOCKED currently not supported with MySQL
|
||||
return sql + " for update";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean.config.dbplatform.oracle;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.BasicSqlAnsiLimiter;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
@@ -10,13 +10,13 @@ import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.RownumSqlLimiter;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.Oracle10Ddl;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
* Oracle specific platform.
|
||||
*/
|
||||
public class OraclePlatform extends DatabasePlatform {
|
||||
|
||||
@@ -28,16 +28,22 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
this.dbEncrypt = new OracleDbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
|
||||
this.platformDdl = new Oracle10Ddl(this);
|
||||
this.historySupport = new OracleDbHistorySupport();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
dbIdentity.setSupportsIdentity(true);
|
||||
dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
this.likeClauseRaw = "like ?";
|
||||
|
||||
this.exceptionTranslator =
|
||||
new SqlErrorCodes()
|
||||
//.addAcquireLock("")
|
||||
.addDuplicateKey("1")
|
||||
.addDataIntegrity("2291")
|
||||
.build();
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
@@ -80,4 +86,9 @@ public class OraclePlatform extends DatabasePlatform {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append('|').append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package io.ebean.config.dbplatform.postgres;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.PlatformIdGenerator;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
@@ -36,7 +33,6 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
this.platformDdl = new PostgresDdl(this);
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
@@ -90,14 +86,6 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
return new DbPlatformType("geometry(" + type + "," + srid + ")");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DdlHandler instance for generating DDL for the specific platform.
|
||||
*/
|
||||
@Override
|
||||
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
|
||||
return this.platformDdl.createDdlHandler(serverConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean.config.dbplatform.sqlanywhere;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package io.ebean.config.dbplatform.sqlite;
|
||||
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.SQLiteDdl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
@@ -14,20 +13,23 @@ public class SQLitePlatform extends DatabasePlatform {
|
||||
public SQLitePlatform() {
|
||||
super();
|
||||
this.platform = Platform.SQLITE;
|
||||
this.platformDdl = new SQLiteDdl(this);
|
||||
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setSupportsSequence(false);
|
||||
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
|
||||
|
||||
this.booleanDbType = Types.INTEGER;
|
||||
this.likeClauseRaw = "like ?";
|
||||
this.likeClauseEscaped = "like ?";
|
||||
|
||||
dbTypeMap.put(DbType.BIT, new DbPlatformType("int default 0"));
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int default 0"));
|
||||
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("integer"));
|
||||
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("integer"));
|
||||
}
|
||||
|
||||
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
|
||||
sb.append(ch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.ebean.config.dbplatform.sqlserver;
|
||||
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.DbType;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.config.dbplatform.SqlErrorCodes;
|
||||
import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,6 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
this.selectCountWithAlias = true;
|
||||
this.sqlLimiter = new SqlServerSqlLimiter();
|
||||
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
|
||||
this.platformDdl = new SqlServerDdl(this);
|
||||
this.historySupport = new SqlServerHistorySupport();
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
@@ -40,8 +39,9 @@ public class SqlServerPlatform extends DatabasePlatform {
|
||||
|
||||
this.openQuote = "[";
|
||||
this.closeQuote = "]";
|
||||
this.specialLikeCharacters = new char[]{'%', '_', '['};
|
||||
this.likeClause = "like ? COLLATE Latin1_General_BIN";
|
||||
this.likeSpecialCharacters = new char[]{'%', '_', '['};
|
||||
this.likeClauseRaw = "like ? collate Latin1_General_BIN";
|
||||
this.likeClauseEscaped = "like ? collate Latin1_General_BIN";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
|
||||
|
||||
@@ -1,103 +1,52 @@
|
||||
package io.ebean.dbmigration;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
import io.ebean.Platform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebean.dbmigration.migration.Migration;
|
||||
import io.ebean.dbmigration.migrationreader.MigrationXmlWriter;
|
||||
import io.ebean.dbmigration.model.CurrentModel;
|
||||
import io.ebean.dbmigration.model.MConfiguration;
|
||||
import io.ebean.dbmigration.model.MigrationModel;
|
||||
import io.ebean.dbmigration.model.MigrationVersion;
|
||||
import io.ebean.dbmigration.model.ModelContainer;
|
||||
import io.ebean.dbmigration.model.ModelDiff;
|
||||
import io.ebean.dbmigration.model.PlatformDdlWriter;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.extraddl.model.DdlScript;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdl;
|
||||
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Generates DB Migration xml and sql scripts.
|
||||
* Generates DDL migration scripts based on changes to the current model.
|
||||
*
|
||||
* <p>
|
||||
* Reads the prior migrations and compares with the current model of the EbeanServer
|
||||
* and generates a migration 'diff' in the form of xml document with the logical schema
|
||||
* changes and a series of sql scripts to apply, rollback the applied changes if necessary
|
||||
* and drop objects (drop tables, drop columns).
|
||||
* </p>
|
||||
* <p>
|
||||
* This does not run the migration or ddl scripts but just generates them.
|
||||
* Typically this is run as a main method in src/test once a developer is happy
|
||||
* with the next set of changes to the model.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example: Run for a single specific platform</h3>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
* // optionally specify the version and name
|
||||
* //System.setProperty("ddl.migration.version", "1.1");
|
||||
* //System.setProperty("ddl.migration.name", "add bars");
|
||||
*
|
||||
* migration.generateMigration();
|
||||
* // generate a migration using drops from a prior version
|
||||
* //System.setProperty("ddl.migration.pendingDropsFor", "1.2");
|
||||
*
|
||||
* DbMigration migration = DbMigration.create();
|
||||
*
|
||||
* migration.setPlatform(Platform.POSTGRES);
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public class DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
|
||||
|
||||
private static final String initialVersion = "1.0";
|
||||
|
||||
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
|
||||
public interface DbMigration {
|
||||
|
||||
/**
|
||||
* Set to true if DbMigration run with online EbeanServer instance.
|
||||
* Create a DbMigration implementation to use.
|
||||
*/
|
||||
protected final boolean online;
|
||||
static DbMigration create() {
|
||||
|
||||
protected SpiEbeanServer server;
|
||||
|
||||
protected DbMigrationConfig migrationConfig;
|
||||
|
||||
protected String pathToResources = "src/main/resources";
|
||||
|
||||
protected DatabasePlatform databasePlatform;
|
||||
|
||||
protected List<Pair> platforms = new ArrayList<>();
|
||||
|
||||
protected ServerConfig serverConfig;
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
*/
|
||||
public DbMigration() {
|
||||
this.online = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create using online EbeanServer.
|
||||
*/
|
||||
public DbMigration(EbeanServer server) {
|
||||
this.online = true;
|
||||
setServer(server);
|
||||
Iterator<DbMigration> loader = ServiceLoader.load(DbMigration.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for DbMigration?");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,33 +54,18 @@ public class DbMigration {
|
||||
* <p>
|
||||
* This defaults to maven style 'src/main/resources'.
|
||||
*/
|
||||
public void setPathToResources(String pathToResources) {
|
||||
this.pathToResources = pathToResources;
|
||||
}
|
||||
void setPathToResources(String pathToResources);
|
||||
|
||||
/**
|
||||
* Set the server to use to determine the current model.
|
||||
* Typically this is not called explicitly.
|
||||
*/
|
||||
public void setServer(EbeanServer ebeanServer) {
|
||||
this.server = (SpiEbeanServer) ebeanServer;
|
||||
setServerConfig(server.getServerConfig());
|
||||
}
|
||||
void setServer(EbeanServer ebeanServer);
|
||||
|
||||
/**
|
||||
* Set the serverConfig to use. Typically this is not called explicitly.
|
||||
*/
|
||||
public void setServerConfig(ServerConfig config) {
|
||||
if (this.serverConfig == null) {
|
||||
this.serverConfig = config;
|
||||
}
|
||||
if (migrationConfig == null) {
|
||||
this.migrationConfig = serverConfig.getMigrationConfig();
|
||||
}
|
||||
if (constraintNaming == null) {
|
||||
this.constraintNaming = serverConfig.getConstraintNaming();
|
||||
}
|
||||
}
|
||||
void setServerConfig(ServerConfig config);
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
@@ -139,9 +73,7 @@ public class DbMigration {
|
||||
* If not set this defaults to the platform of the default server.
|
||||
* </p>
|
||||
*/
|
||||
public void setPlatform(Platform platform) {
|
||||
setPlatform(getPlatform(platform));
|
||||
}
|
||||
void setPlatform(Platform platform);
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
@@ -149,12 +81,17 @@ public class DbMigration {
|
||||
* If not set this defaults to the platform of the default server.
|
||||
* </p>
|
||||
*/
|
||||
public void setPlatform(DatabasePlatform databasePlatform) {
|
||||
this.databasePlatform = databasePlatform;
|
||||
if (!online) {
|
||||
DbOffline.setPlatform(databasePlatform.getPlatform());
|
||||
}
|
||||
}
|
||||
void setPlatform(DatabasePlatform databasePlatform);
|
||||
|
||||
/**
|
||||
* Set to false to turn off strict mode.
|
||||
* <p>
|
||||
* Strict mode checks that a column changed to non-null on an existing table via DB migration has a default
|
||||
* value specified. Set this to false if that isn't the case but it is known that all the existing rows have
|
||||
* a value specified (there are no existing null values for the column).
|
||||
* </p>
|
||||
*/
|
||||
void setStrictMode(boolean strictMode);
|
||||
|
||||
/**
|
||||
* Add an additional platform to write the migration DDL.
|
||||
@@ -163,9 +100,7 @@ public class DbMigration {
|
||||
* from the migration (e.g. generate migration sql for MySql, Postgres and Oracle).
|
||||
* </p>
|
||||
*/
|
||||
public void addPlatform(Platform platform, String prefix) {
|
||||
platforms.add(new Pair(getPlatform(platform), prefix));
|
||||
}
|
||||
void addPlatform(Platform platform, String prefix);
|
||||
|
||||
/**
|
||||
* Generate the next migration xml file and associated apply and rollback sql scripts.
|
||||
@@ -175,384 +110,29 @@ public class DbMigration {
|
||||
* <h3>Example: Run for a single specific platform</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPlatform(Platform.POSTGRES);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
* <p>
|
||||
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = new DbMigration();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* DbMigration migration = DbMigration.create();
|
||||
*
|
||||
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
|
||||
* migration.addPlatform(DbPlatformName.MYSQL, "mysql");
|
||||
* migration.addPlatform(DbPlatformName.ORACLE, "mysql");
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
*
|
||||
* migration.generateMigration();
|
||||
* migration.addPlatform(Platform.POSTGRES, "pg");
|
||||
* migration.addPlatform(Platform.MYSQL, "mysql");
|
||||
* migration.addPlatform(Platform.ORACLE, "oracle");
|
||||
*
|
||||
* migration.generateMigration();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return the version of the generated migration or null
|
||||
*/
|
||||
public void generateMigration() throws IOException {
|
||||
|
||||
// use this flag to stop other plugins like full DDL generation
|
||||
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
|
||||
setPlatform(platforms.get(0).platform);
|
||||
}
|
||||
}
|
||||
setDefaults();
|
||||
try {
|
||||
Request request = createRequest();
|
||||
|
||||
if (platforms.isEmpty()) {
|
||||
generateExtraDdl(request.migrationDir, databasePlatform);
|
||||
}
|
||||
|
||||
String pendingVersion = generatePendingDrop();
|
||||
if (pendingVersion != null) {
|
||||
generatePendingDrop(request, pendingVersion);
|
||||
} else {
|
||||
generateDiff(request);
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (!online) {
|
||||
DbOffline.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate "repeatable" migration scripts.
|
||||
* <p>
|
||||
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
|
||||
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
|
||||
* migration runner.
|
||||
* </p>
|
||||
*/
|
||||
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 (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());
|
||||
|
||||
logger.info("writing repeatable script {}", fullName);
|
||||
|
||||
File file = new File(migrationDir, fullName);
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
writer.write(script.getValue());
|
||||
writer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private String repeatableMigrationName(String scriptName) {
|
||||
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the diff migration.
|
||||
*/
|
||||
private void generateDiff(Request request) throws IOException {
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
logger.info("Pending un-applied drops in versions {}", pendingDrops);
|
||||
}
|
||||
|
||||
Migration migration = request.createDiffMigration();
|
||||
if (migration == null) {
|
||||
logger.info("no changes detected - no migration written");
|
||||
} else {
|
||||
// there were actually changes to write
|
||||
generateMigration(request, migration, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the migration based on the pendingDrops from a prior version.
|
||||
*/
|
||||
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
|
||||
|
||||
Migration migration = request.migrationForPendingDrop(pendingVersion);
|
||||
|
||||
generateMigration(request, migration, pendingVersion);
|
||||
|
||||
List<String> pendingDrops = request.getPendingDrops();
|
||||
if (!pendingDrops.isEmpty()) {
|
||||
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
|
||||
}
|
||||
}
|
||||
|
||||
private Request createRequest() {
|
||||
return new Request();
|
||||
}
|
||||
|
||||
private class Request {
|
||||
|
||||
final File migrationDir;
|
||||
final File modelDir;
|
||||
final MigrationModel migrationModel;
|
||||
final CurrentModel currentModel;
|
||||
final ModelContainer migrated;
|
||||
final ModelContainer current;
|
||||
|
||||
private Request() {
|
||||
this.migrationDir = getMigrationDirectory();
|
||||
this.modelDir = getModelDirectory(migrationDir);
|
||||
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
|
||||
this.migrated = migrationModel.read();
|
||||
this.currentModel = new CurrentModel(server, constraintNaming);
|
||||
this.current = currentModel.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the migration for the pending drops for a given version.
|
||||
*/
|
||||
public Migration migrationForPendingDrop(String pendingVersion) {
|
||||
|
||||
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
|
||||
|
||||
// register any remaining pending drops
|
||||
migrated.registerPendingHistoryDropColumns(current);
|
||||
return migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of versions that have pending un-applied drops.
|
||||
*/
|
||||
public List<String> getPendingDrops() {
|
||||
return migrated.getPendingDrops();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return the diff of the current model to the migration model.
|
||||
*/
|
||||
public Migration createDiffMigration() {
|
||||
ModelDiff diff = new ModelDiff(migrated);
|
||||
diff.compareTo(current);
|
||||
return diff.isEmpty() ? null : diff.getMigration();
|
||||
}
|
||||
}
|
||||
|
||||
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
|
||||
|
||||
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
|
||||
|
||||
logger.info("generating migration:{}", fullVersion);
|
||||
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
|
||||
logger.warn("migration already exists, not generating DDL");
|
||||
|
||||
} else {
|
||||
if (!platforms.isEmpty()) {
|
||||
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
|
||||
|
||||
} else if (databasePlatform != null) {
|
||||
// writer needs the current model to provide table/column details for
|
||||
// history ddl generation (triggers, history tables etc)
|
||||
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
|
||||
PlatformDdlWriter writer = createDdlWriter(databasePlatform);
|
||||
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the next pending drop changeSet should be generated as the next migration.
|
||||
*/
|
||||
private String generatePendingDrop() {
|
||||
|
||||
String nextDrop = System.getProperty("ddl.migration.pendingDropsFor");
|
||||
if (nextDrop != null) {
|
||||
return nextDrop;
|
||||
}
|
||||
return migrationConfig.getGeneratePendingDrop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full version for the migration being generated.
|
||||
* <p>
|
||||
* The full version can contain a comment suffix after a "__" double underscore.
|
||||
*/
|
||||
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
|
||||
|
||||
String version = migrationConfig.getVersion();
|
||||
if (version == null) {
|
||||
version = migrationModel.getNextVersion(initialVersion);
|
||||
}
|
||||
|
||||
String fullVersion = migrationConfig.getApplyPrefix() + version;
|
||||
if (migrationConfig.getName() != null) {
|
||||
fullVersion += "__" + toUnderScore(migrationConfig.getName());
|
||||
|
||||
} else if (dropsFor != null) {
|
||||
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
|
||||
|
||||
} else if (version.equals(initialVersion)) {
|
||||
fullVersion += "__initial";
|
||||
}
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace spaces with underscores.
|
||||
*/
|
||||
private String toUnderScore(String name) {
|
||||
return name.replace(' ', '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write any extra platform ddl.
|
||||
*/
|
||||
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
|
||||
|
||||
for (Pair pair : platforms) {
|
||||
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
|
||||
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
|
||||
File subPath = platformWriter.subPath(writePath, pair.prefix);
|
||||
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
|
||||
|
||||
generateExtraDdl(subPath, pair.platform);
|
||||
}
|
||||
}
|
||||
|
||||
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform) {
|
||||
return new PlatformDdlWriter(platform, serverConfig, migrationConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the migration xml.
|
||||
*/
|
||||
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
|
||||
|
||||
String modelFile = fullVersion + migrationConfig.getModelSuffix();
|
||||
File file = new File(resourcePath, modelFile);
|
||||
if (file.exists()) {
|
||||
return false;
|
||||
}
|
||||
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
|
||||
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
|
||||
xmlWriter.write(dbMigration, file);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default server and platform if necessary.
|
||||
*/
|
||||
protected void setDefaults() {
|
||||
if (server == null) {
|
||||
setServer(Ebean.getDefaultServer());
|
||||
}
|
||||
if (databasePlatform == null && platforms.isEmpty()) {
|
||||
// not explicitly set not set a list of platforms so
|
||||
// default to the platform of the default server
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file path to write the xml and sql to.
|
||||
*/
|
||||
protected File getMigrationDirectory() {
|
||||
|
||||
// path to src/main/resources in typical maven project
|
||||
File resourceRootDir = new File(pathToResources);
|
||||
String resourcePath = migrationConfig.getMigrationPath();
|
||||
|
||||
// expect to be a path to something like - src/main/resources/dbmigration/model
|
||||
File path = new File(resourceRootDir, resourcePath);
|
||||
if (!path.exists()) {
|
||||
if (!path.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration directory exists at {}", path.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the model directory (relative to the migration directory).
|
||||
*/
|
||||
protected File getModelDirectory(File migrationDirectory) {
|
||||
String modelPath = migrationConfig.getModelPath();
|
||||
if (modelPath == null || modelPath.isEmpty()) {
|
||||
return migrationDirectory;
|
||||
}
|
||||
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
|
||||
if (!modelDir.exists() && !modelDir.mkdirs()) {
|
||||
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
|
||||
}
|
||||
return modelDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DatabasePlatform given the platform key.
|
||||
*/
|
||||
protected DatabasePlatform getPlatform(Platform platform) {
|
||||
switch (platform) {
|
||||
case H2:
|
||||
return new H2Platform();
|
||||
case POSTGRES:
|
||||
return new PostgresPlatform();
|
||||
case MYSQL:
|
||||
return new MySqlPlatform();
|
||||
case ORACLE:
|
||||
return new OraclePlatform();
|
||||
case SQLSERVER:
|
||||
return new SqlServerPlatform();
|
||||
case DB2:
|
||||
return new DB2Platform();
|
||||
case SQLITE:
|
||||
return new SQLitePlatform();
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Platform missing? " + platform);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds a platform and prefix. Used to generate multiple platform specific DDL
|
||||
* for a single migration.
|
||||
*/
|
||||
public static class Pair {
|
||||
|
||||
/**
|
||||
* The platform to generate the DDL for.
|
||||
*/
|
||||
public final DatabasePlatform platform;
|
||||
|
||||
/**
|
||||
* A prefix included into the file/resource names indicating the platform.
|
||||
*/
|
||||
public final String prefix;
|
||||
|
||||
public Pair(DatabasePlatform platform, String prefix) {
|
||||
this.platform = platform;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
}
|
||||
|
||||
String generateMigration() throws IOException;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
/**
|
||||
* Generation DDL migration scripts based on changes to the model.
|
||||
* <p>
|
||||
* You can use <code>DbMigration</code> to compare the entity bean model to its prior state
|
||||
* and generate DDL for the differences - adding tables, columns etc.
|
||||
* </p>
|
||||
* Generates DDL migration scripts based on changes to the model.
|
||||
*/
|
||||
package io.ebean.dbmigration;
|
||||
|
||||
@@ -24,4 +24,13 @@ public interface BeanQueryRequest<T> {
|
||||
*/
|
||||
Query<T> getQuery();
|
||||
|
||||
/**
|
||||
* Return true if multi-value binding using Array or Table Values is supported.
|
||||
*/
|
||||
boolean isMultiValueIdSupported();
|
||||
|
||||
/**
|
||||
* Return true if multi-value binding is supported for this value type.
|
||||
*/
|
||||
boolean isMultiValueSupported(Class<?> valueType);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package io.ebean.event.changelog;
|
||||
|
||||
import io.ebean.ValuePair;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A bean insert, update or delete change sent as part of a ChangeSet.
|
||||
*/
|
||||
@@ -12,68 +8,74 @@ public class BeanChange {
|
||||
/**
|
||||
* The underling base table name.
|
||||
*/
|
||||
String table;
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* The tenantId value.
|
||||
*/
|
||||
Object tenantId;
|
||||
|
||||
private Object tenantId;
|
||||
|
||||
/**
|
||||
* The id value.
|
||||
*/
|
||||
Object id;
|
||||
private Object id;
|
||||
|
||||
/**
|
||||
* The INSERT, UPDATE or DELETE change type.
|
||||
*/
|
||||
ChangeType type;
|
||||
private ChangeType event;
|
||||
|
||||
/**
|
||||
* The time the bean change was created.
|
||||
*/
|
||||
long eventTime;
|
||||
private long eventTime;
|
||||
|
||||
/**
|
||||
* The values for insert or update. Note that null values are not included for insert.
|
||||
* The change in JSON form.
|
||||
*/
|
||||
Map<String, ValuePair> values;
|
||||
private String data;
|
||||
|
||||
/**
|
||||
* Construct with all the details.
|
||||
* The change in JSON form.
|
||||
*/
|
||||
public BeanChange(String table, Object tenantId, Object id, ChangeType type, Map<String, ValuePair> values) {
|
||||
this.table = table;
|
||||
this.tenantId = tenantId;
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.eventTime = System.currentTimeMillis();
|
||||
this.values = values;
|
||||
}
|
||||
private String oldData;
|
||||
|
||||
/**
|
||||
* Default constructor for JSON tools.
|
||||
* Constructor for JSON tools.
|
||||
*/
|
||||
public BeanChange() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with change as JSON.
|
||||
*/
|
||||
public BeanChange(String type, Object tenantId, Object id, ChangeType event, String data, String oldData) {
|
||||
this.type = type;
|
||||
this.tenantId = tenantId;
|
||||
this.id = id;
|
||||
this.event = event;
|
||||
this.eventTime = System.currentTimeMillis();
|
||||
this.data = data;
|
||||
this.oldData = oldData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with change as JSON.
|
||||
*/
|
||||
public BeanChange(String table, Object tenantId, Object id, ChangeType event, String data) {
|
||||
this(table, tenantId , id , event , data , null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "table:" + table + " tenantId: " + tenantId + " id:" + id + " values:" + values;
|
||||
return "type:" + type + " tenantId: " + tenantId + " id:" + id + " data:" + data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object type (typically table name).
|
||||
*/
|
||||
public String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object type (for JSON tools).
|
||||
*/
|
||||
public void setTable(String table) {
|
||||
this.table = table;
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,13 +85,6 @@ public class BeanChange {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean id (for JSON tools).
|
||||
*/
|
||||
public void setTenantId(Object tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the object id.
|
||||
*/
|
||||
@@ -97,25 +92,11 @@ public class BeanChange {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean id (for JSON tools).
|
||||
*/
|
||||
public void setId(Object id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the change type (INSERT, UPDATE or DELETE).
|
||||
*/
|
||||
public ChangeType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type (for JSON tools).
|
||||
*/
|
||||
public void setType(ChangeType type) {
|
||||
this.type = type;
|
||||
public ChangeType getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,23 +107,16 @@ public class BeanChange {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event time in epoch millis.
|
||||
* Return the change data in JSON form.
|
||||
*/
|
||||
public void setEventTime(long eventTime) {
|
||||
this.eventTime = eventTime;
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value pairs. For inserts the ValuePair oldValue is always null.
|
||||
* Return the old data in JSON form.
|
||||
*/
|
||||
public Map<String, ValuePair> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value pairs (for JSON tools).
|
||||
*/
|
||||
public void setValues(Map<String, ValuePair> values) {
|
||||
this.values = values;
|
||||
public String getOldData() {
|
||||
return oldData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MetaBeanInfo {
|
||||
|
||||
/**
|
||||
* Collect the current query plan statistics return the non-empty statistics.
|
||||
*/
|
||||
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
|
||||
|
||||
/**
|
||||
* Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions).
|
||||
*/
|
||||
List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
|
||||
|
||||
}
|
||||
@@ -8,17 +8,12 @@ import java.util.List;
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Return the MetaBeanInfo for a bean type.
|
||||
* Collect and return the transaction execution metrics.
|
||||
*/
|
||||
MetaBeanInfo getMetaBeanInfo(Class<?> beanClass);
|
||||
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
|
||||
|
||||
/**
|
||||
* Return all the MetaBeanInfo.
|
||||
*/
|
||||
List<MetaBeanInfo> getMetaBeanInfoList();
|
||||
|
||||
/**
|
||||
* Collect and return the query plan statistics for all the beans.
|
||||
* 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).
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -14,6 +16,11 @@ public interface MetaQueryPlanStatistic {
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the profile location.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this query plan was tuned by AutoTune.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
|
||||
/**
|
||||
* Timed execution statistics.
|
||||
*/
|
||||
public interface MetaTimedMetric {
|
||||
|
||||
/**
|
||||
* Return the metric name.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the metric location if defined.
|
||||
*/
|
||||
String getLocation();
|
||||
|
||||
/**
|
||||
* Return the time the counters started from.
|
||||
*/
|
||||
long getStartTime();
|
||||
|
||||
/**
|
||||
* Return the total count.
|
||||
*/
|
||||
long getCount();
|
||||
|
||||
/**
|
||||
* Return the total execution time.
|
||||
*/
|
||||
long getTotal();
|
||||
|
||||
/**
|
||||
* Return the max execution time.
|
||||
*/
|
||||
long getMax();
|
||||
|
||||
/**
|
||||
* Return the mean execution time.
|
||||
*/
|
||||
long getMean();
|
||||
}
|
||||
@@ -35,7 +35,7 @@ Customer customer = Ebean.find(Customer.class)
|
||||
.fetch("billingAddress");
|
||||
.fetch("shippingAddress");
|
||||
.setId(7)
|
||||
.findUnique();
|
||||
.findOne();
|
||||
|
||||
|
||||
Address billAddr = customer.getBillingAddress();
|
||||
|
||||
@@ -28,7 +28,7 @@ Customer customer =
|
||||
.setId(7)
|
||||
.fetch("billingAddress")
|
||||
.fetch("shippingAddress")
|
||||
.findUnique();
|
||||
.findOne();
|
||||
|
||||
Address billAddr = customer.getBillingAddress();
|
||||
Address shipAddr = customer.getShippingAddress();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebean.plugin;
|
||||
|
||||
import io.ebean.Query;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.BeanPersistListener;
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -20,6 +20,11 @@ public interface BeanType<T> {
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Return the profileId of the bean type.
|
||||
*/
|
||||
short getProfileId();
|
||||
|
||||
/**
|
||||
* Return the full name of the bean type.
|
||||
*/
|
||||
@@ -180,7 +185,7 @@ public interface BeanType<T> {
|
||||
/**
|
||||
* Add the discriminator value to the query if needed.
|
||||
*/
|
||||
void addInheritanceWhere(SpiQuery<?> query);
|
||||
void addInheritanceWhere(Query<?> query);
|
||||
|
||||
/**
|
||||
* Return the root bean type for an inheritance hierarchy.
|
||||
|
||||
@@ -47,4 +47,9 @@ public interface SpiServer extends EbeanServer {
|
||||
*/
|
||||
DataSource getDataSource();
|
||||
|
||||
/**
|
||||
* Return the associated read only DataSource for this EbeanServer instance (can be null).
|
||||
*/
|
||||
DataSource getReadOnlyDataSource();
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebean.common;
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.config.ServerConfig;
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.config.ContainerConfig;
|
||||
|
||||
/**
|
||||
* Provides shutdown of the entire container.
|
||||
*/
|
||||
public interface SpiContainerFactory {
|
||||
|
||||
/**
|
||||
* Create the Container that builds EbeanServer instances.
|
||||
*/
|
||||
SpiContainer create(ContainerConfig containerConfig);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* JSON service that Ebean is expected to provide.
|
||||
*
|
||||
* Supports converting between JSON content and simple java Maps/Lists.
|
||||
*/
|
||||
public interface SpiJsonService {
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json.
|
||||
*/
|
||||
String write(Object object) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the writer.
|
||||
*/
|
||||
void write(Object object, Writer writer) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the jsonGenerator.
|
||||
*/
|
||||
void write(Object object, JsonGenerator jsonGenerator) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the collection as json array to the jsonGenerator.
|
||||
*/
|
||||
void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map additionally specifying if the returned map should
|
||||
* be modify aware meaning that it can detect when it has been modified.
|
||||
*/
|
||||
Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map.
|
||||
*/
|
||||
Map<String, Object> parseObject(String json) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
Map<String, Object> parseObject(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser.
|
||||
*/
|
||||
Map<String, Object> parseObject(JsonParser parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser and a starting token.
|
||||
* <p>
|
||||
* Used when the first token is checked to see if the value is null prior to calling this.
|
||||
* </p>
|
||||
*/
|
||||
Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a modify aware List.
|
||||
*/
|
||||
<T> List<T> parseList(String json, boolean modifyAware) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List.
|
||||
*/
|
||||
List<Object> parseList(String json) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a Reader.
|
||||
*/
|
||||
List<Object> parseList(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a JsonParser.
|
||||
*/
|
||||
List<Object> parseList(JsonParser parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
<T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
Object parse(String json) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
Object parse(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
Object parse(JsonParser parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json returning a Set that might be modify aware.
|
||||
*/
|
||||
<T> Set<T> parseSet(String json, boolean modifyAware) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json returning as a Set taking into account the current token.
|
||||
*/
|
||||
<T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
|
||||
/**
|
||||
* Factory for creating profile locations.
|
||||
*/
|
||||
public interface SpiProfileLocationFactory {
|
||||
|
||||
/**
|
||||
* Create a profile location.
|
||||
*/
|
||||
ProfileLocation create();
|
||||
|
||||
/**
|
||||
* Create a profile location with a line number.
|
||||
*/
|
||||
ProfileLocation create(int lineNumber, String label);
|
||||
|
||||
/**
|
||||
* Create a known location.
|
||||
*/
|
||||
ProfileLocation createAt(String location);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import io.ebean.RawSql;
|
||||
import io.ebean.RawSqlBuilder;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
/**
|
||||
* Service provided by Ebean for parsing and column mapping raw SQL queries.
|
||||
*/
|
||||
public interface SpiRawSqlService {
|
||||
|
||||
/**
|
||||
* Create based on a JDBC ResultSet.
|
||||
*/
|
||||
RawSql resultSet(ResultSet resultSet, String... propertyNames);
|
||||
|
||||
/**
|
||||
* Parse the SQL determining column mapping.
|
||||
*/
|
||||
RawSqlBuilder parsed(String sql);
|
||||
|
||||
/**
|
||||
* Unparsed SQL so explicit column mapping expected.
|
||||
*/
|
||||
RawSqlBuilder unparsed(String sql);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package io.ebean.text;
|
||||
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.Query;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebean.util.SplitName;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package io.ebean.text.json;
|
||||
|
||||
import io.ebeaninternal.server.type.ModifyAwareList;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.service.SpiJsonService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -19,32 +20,43 @@ import java.util.Set;
|
||||
*/
|
||||
public class EJson {
|
||||
|
||||
private static SpiJsonService plugin = init();
|
||||
|
||||
private static SpiJsonService init() {
|
||||
|
||||
Iterator<SpiJsonService> loader = ServiceLoader.load(SpiJsonService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
throw new IllegalStateException("No service implementation found for SpiJsonService?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json.
|
||||
*/
|
||||
public static String write(Object object) throws IOException {
|
||||
return EJsonWriter.write(object);
|
||||
return plugin.write(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the writer.
|
||||
*/
|
||||
public static void write(Object object, Writer writer) throws IOException {
|
||||
EJsonWriter.write(object, writer);
|
||||
plugin.write(object, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the jsonGenerator.
|
||||
*/
|
||||
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
|
||||
EJsonWriter.write(object, jsonGenerator);
|
||||
plugin.write(object, jsonGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the collection as json array to the jsonGenerator.
|
||||
*/
|
||||
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
|
||||
EJsonWriter.writeCollection(collection, jsonGenerator);
|
||||
plugin.writeCollection(collection, jsonGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,35 +64,35 @@ public class EJson {
|
||||
* be modify aware meaning that it can detect when it has been modified.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseObject(json, modifyAware);
|
||||
return plugin.parseObject(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(String json) throws IOException {
|
||||
return EJsonReader.parseObject(json);
|
||||
return plugin.parseObject(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseObject(reader, modifyAware);
|
||||
return plugin.parseObject(reader, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(Reader reader) throws IOException {
|
||||
return EJsonReader.parseObject(reader);
|
||||
return plugin.parseObject(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parseObject(parser);
|
||||
return plugin.parseObject(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,86 +102,77 @@ public class EJson {
|
||||
* </p>
|
||||
*/
|
||||
public static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
return EJsonReader.parseObject(parser, token);
|
||||
return plugin.parseObject(parser, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a modify aware List.
|
||||
*/
|
||||
public static List<Object> parseList(String json, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseList(json, modifyAware);
|
||||
public static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
|
||||
return plugin.parseList(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List.
|
||||
*/
|
||||
public static List<Object> parseList(String json) throws IOException {
|
||||
return EJsonReader.parseList(json);
|
||||
return plugin.parseList(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a Reader.
|
||||
*/
|
||||
public static List<Object> parseList(Reader reader) throws IOException {
|
||||
return EJsonReader.parseList(reader);
|
||||
return plugin.parseList(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a JsonParser.
|
||||
*/
|
||||
public static List<Object> parseList(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parseList(parser, false);
|
||||
return plugin.parseList(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Object> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return (List<Object>) EJsonReader.parse(parser, currentToken, false);
|
||||
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return plugin.parseList(parser, currentToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
public static Object parse(String json) throws IOException {
|
||||
return EJsonReader.parse(json);
|
||||
return plugin.parse(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
public static Object parse(Reader reader) throws IOException {
|
||||
return EJsonReader.parse(reader);
|
||||
return plugin.parse(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
public static Object parse(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parse(parser);
|
||||
return plugin.parse(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning a Set that might be modify aware.
|
||||
*/
|
||||
public static Set parseSet(String json, boolean modifyAware) throws IOException {
|
||||
List<Object> list = parseList(json, modifyAware);
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (modifyAware) {
|
||||
return ((ModifyAwareList) list).asSet();
|
||||
} else {
|
||||
return new LinkedHashSet<>(list);
|
||||
}
|
||||
public static <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
|
||||
return plugin.parseSet(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning as a Set taking into account the current token.
|
||||
*/
|
||||
public static Set<Object> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return new LinkedHashSet<>(parseList(parser, currentToken));
|
||||
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return plugin.parseSet(parser, currentToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package io.ebean.util;
|
||||
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.annotation.Where;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Annotation utility methods to find annotations recursively - taken from the spring framework.
|
||||
*/
|
||||
public class AnnotationUtil {
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
|
||||
*/
|
||||
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
|
||||
return annotation.annotationType().getName().startsWith("java.lang.annotation");
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}.
|
||||
* <p>
|
||||
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
|
||||
* <p>
|
||||
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
|
||||
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
|
||||
* It also does not filter out platform dependent annotations!
|
||||
*/
|
||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
// check if directly present, if not, start search for meta-annotations.
|
||||
Annotation[] anns = annotatedElement.getAnnotations();
|
||||
if (anns.length == 0) {
|
||||
return null; // no annotations present, so searching for meta annotations not required
|
||||
}
|
||||
|
||||
// As we need the anns array anyway, we iterate over this instead
|
||||
// of using annotatedElement.getAnnotation(...) which is synchronized internally
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType() == annotationType) {
|
||||
return (A) ann;
|
||||
}
|
||||
}
|
||||
|
||||
return findAnnotation(anns, annotationType, new HashSet<>());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a single {@link Annotation} of {@code annotationType} on the supplied class.
|
||||
* <p>Meta-annotations will be searched if the annotation is not directly present on
|
||||
* the supplied element.
|
||||
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
|
||||
*/
|
||||
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
// check if directly present, if not, start search for meta-annotations.
|
||||
Annotation[] anns = clazz.getAnnotations();
|
||||
if (anns.length != 0) {
|
||||
for (Annotation ann : anns) {
|
||||
if (ann.annotationType() == annotationType) {
|
||||
return (A) ann;
|
||||
}
|
||||
}
|
||||
|
||||
A ann = findAnnotation(anns, annotationType, new HashSet<>());
|
||||
if (ann != null) {
|
||||
return ann;
|
||||
}
|
||||
}
|
||||
// no meta-annotation present at this class - traverse to superclass
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first
|
||||
* found annotation is returned)
|
||||
*/
|
||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Platform platform) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> anns = findAnnotations(annotatedElement, annotationType);
|
||||
return getPlatformMatchingAnnotation(anns, platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all annotations recusively for a class and its superclasses.
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> ret = new LinkedHashSet<>();
|
||||
Set<Annotation> visited = new HashSet<>();
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
findMetaAnnotations(clazz, annotationType, ret, visited);
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm avoiding endless recursion by tracking which
|
||||
* annotations have already been visited.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends Annotation> A findAnnotation(Annotation[] anns, Class<A> annotationType, Set<Annotation> visited) {
|
||||
|
||||
|
||||
for (Annotation ann : anns) {
|
||||
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
|
||||
Annotation[] metaAnns = ann.annotationType().getAnnotations();
|
||||
for (Annotation metaAnn : metaAnns) {
|
||||
if (metaAnn.annotationType() == annotationType) {
|
||||
return (A) metaAnn;
|
||||
}
|
||||
}
|
||||
if (metaAnns.length > 0) {
|
||||
A annotation = findAnnotation(metaAnns, annotationType, visited);
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}.
|
||||
* <p>
|
||||
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
|
||||
* <p>
|
||||
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
|
||||
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> findAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
Set<A> ret = new LinkedHashSet<>();
|
||||
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<>());
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search algorithm avoiding endless recursion by tracking which
|
||||
* annotations have already been visited.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends Annotation> void findMetaAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType, Set<A> ret, Set<Annotation> visited) {
|
||||
|
||||
Annotation[] anns = annotatedElement.getAnnotations();
|
||||
for (Annotation ann : anns) {
|
||||
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
|
||||
if (ann.annotationType() == annotationType) {
|
||||
ret.add((A) ann);
|
||||
} else {
|
||||
Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType);
|
||||
if (repeatableValueMethod != null) {
|
||||
try {
|
||||
A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann);
|
||||
for (Annotation repeatedAnn : repeatedAnns) {
|
||||
ret.add((A) repeatedAnn);
|
||||
findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited);
|
||||
}
|
||||
} catch (Exception e) { // catch all exceptions (thrown by invoke)
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
findMetaAnnotations(ann.annotationType(), annotationType, ret, visited);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// caches for getRepeatableValueMethod
|
||||
private static Method getNullMethod() {
|
||||
try {
|
||||
return AnnotationUtil.class.getDeclaredMethod("getNullMethod");
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ConcurrentMap<Annotation, Method> valueMethods = new ConcurrentHashMap<>();
|
||||
// only a non-null-marker the valueMethods - Cache
|
||||
private static final Method nullMethod = getNullMethod();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the <code>value()</code> method for a possible containerAnnotation.
|
||||
* Method is retuned only, if its signature is <code>array of containingType</code>.
|
||||
*/
|
||||
private static <A extends Annotation> Method getRepeatableValueMethod(
|
||||
Annotation containerAnnotation, Class<A> containingType) {
|
||||
|
||||
Method method = valueMethods.get(containerAnnotation);
|
||||
if (method == null) {
|
||||
try {
|
||||
method = containerAnnotation.annotationType().getMethod("value");
|
||||
} catch (NoSuchMethodException e) {
|
||||
method = nullMethod;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Method prev = valueMethods.putIfAbsent(containerAnnotation, method);
|
||||
method = prev == null ? method : prev;
|
||||
}
|
||||
if (method != nullMethod) {
|
||||
Class<?> retType = method.getReturnType();
|
||||
if (retType.isArray() && retType.getComponentType() == containingType) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds a suitable annotation from <code>Set<T> anns</code> for this platform.
|
||||
* To distinguish between platforms, annotation type <code>T</code> must define
|
||||
* a method withthis signature:
|
||||
* <p>
|
||||
* <code>Class<? extends DatabasePlatform>[] platforms() default {};</code>
|
||||
* </p>
|
||||
* The finding rules are:
|
||||
* <ol>
|
||||
* <li>Check if T has method "platforms" if not, return <code>ann[0]</code></code>
|
||||
* <li>find the annotation that is defined for <code>databasePlatform</code></li>
|
||||
* <li>otherwise return the annotation for default platform (platforms = {})</li>
|
||||
* <li>return null
|
||||
* </ol>
|
||||
* (This mechanism is currently used by {@link Where} and {@link Formula})
|
||||
*/
|
||||
public static <T extends Annotation> T getPlatformMatchingAnnotation(Set<T> anns, Platform matchPlatform) {
|
||||
if (anns.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Method getPlatformsMethod = null;
|
||||
T fallback = null;
|
||||
for (T ann : anns) {
|
||||
try {
|
||||
if (getPlatformsMethod == null) {
|
||||
getPlatformsMethod = ann.getClass().getMethod("platforms");
|
||||
}
|
||||
if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) {
|
||||
return ann;
|
||||
}
|
||||
Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann);
|
||||
if (platforms.length == 0) {
|
||||
fallback = ann;
|
||||
} else {
|
||||
for (Platform platform : platforms) {
|
||||
if (matchPlatform == platform) {
|
||||
return ann;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
return ann; // not platform specific - return first one
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package io.ebean.util;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Helper to find classes taking into account the context class loader.
|
||||
*/
|
||||
public class ClassUtil {
|
||||
|
||||
/**
|
||||
* Returns the raw type for the 2nd generic parameter for a subclass.
|
||||
*/
|
||||
public static Class<?> getSecondArgumentType(Class<?> subclass) {
|
||||
Type[] typeArguments = getSuperclassTypeParameter(subclass);
|
||||
if (typeArguments.length != 2) {
|
||||
throw new IllegalArgumentException("Expected type with 2 generic argument types but got "
|
||||
+ typeArguments.length + " - " + Arrays.toString(typeArguments));
|
||||
}
|
||||
|
||||
return getRawType(typeArguments[1]);
|
||||
}
|
||||
|
||||
static Type[] getSuperclassTypeParameter(Class<?> subclass) {
|
||||
Type superclass = subclass.getGenericSuperclass();
|
||||
if (superclass instanceof Class) {
|
||||
throw new RuntimeException("Missing generics type parameters on subclass " + subclass);
|
||||
}
|
||||
return ((ParameterizedType) superclass).getActualTypeArguments();
|
||||
}
|
||||
|
||||
private static Class<?> getRawType(Type type) {
|
||||
|
||||
if (type instanceof Class<?>) {
|
||||
return (Class<?>) type;
|
||||
|
||||
} else if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
Type rawType = parameterizedType.getRawType();
|
||||
if (rawType instanceof Class<?>) {
|
||||
return (Class<?>) rawType;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Unable to obtain raw class type from " + type);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
package io.ebean.util;
|
||||
|
||||
/**
|
||||
* Helper for dot notation property paths.
|
||||
@@ -3,6 +3,7 @@ package io.ebean.util;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Utility String class that supports String manipulation functions.
|
||||
@@ -13,6 +14,10 @@ public class StringHelper {
|
||||
|
||||
private static final char DOUBLE_QUOTE = '"';
|
||||
|
||||
private static final Pattern SPLIT_NAMES = Pattern.compile("[\\s,;]+");
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
/**
|
||||
* parses a String of the form name1='value1' name2='value2'. Note that you
|
||||
* can use either single or double quotes for any particular name value pair
|
||||
@@ -265,66 +270,6 @@ public class StringHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the FIRST string in str that is bounded on the left by
|
||||
* leftBound, and bounded on the right by rightBound. This will return null if
|
||||
* the leftBound is not found within str.
|
||||
* <p>
|
||||
* If leftBound can't be found this returns null.
|
||||
* <p>
|
||||
* This rightBound can't be found then this throws a
|
||||
* StringIndexOutOfBoundsException.
|
||||
*
|
||||
* @param str the base string that we will search for the bounded string.
|
||||
* @param leftBound the left bound of the string.
|
||||
* @param rightBound the right bound of the string.
|
||||
*/
|
||||
public static String getBoundedString(String str, String leftBound, String rightBound)
|
||||
throws RuntimeException {
|
||||
|
||||
if (str == null) {
|
||||
throw new RuntimeException("string to parse is null?");
|
||||
}
|
||||
int startPos = str.indexOf(leftBound);
|
||||
if (startPos > -1) {
|
||||
startPos += leftBound.length();
|
||||
int endPos = str.indexOf(rightBound, startPos);
|
||||
// dp(str+" start:"+startPos+" end:"+endPos);
|
||||
if (endPos == -1) {
|
||||
throw new RuntimeException("Can't find rightBound: " + rightBound);
|
||||
}
|
||||
return str.substring(startPos, endPos);
|
||||
} else {
|
||||
// if no leftBound can be found.. return null... could be in a
|
||||
// search n parse type loop?
|
||||
// this keeps "no tag"==null different from "tag not formed
|
||||
// properly"==StringParsingException
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the String bounded by leftBound & rightBound, and replaces it with
|
||||
* replaceString. Actually removes the left and right bound strings aswell.
|
||||
*/
|
||||
public static String setBoundedString(String str, String leftBound, String rightBound,
|
||||
String replaceString) {
|
||||
|
||||
int startPos = str.indexOf(leftBound);
|
||||
if (startPos > -1) {
|
||||
// startPos = startPos;
|
||||
int endPos = str.indexOf(rightBound, startPos + leftBound.length());
|
||||
if (endPos > -1) {
|
||||
String toReplace = str.substring(startPos, endPos + 1);
|
||||
return replaceString(str, toReplace, replaceString);
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a String and will replace all occurrences of the match
|
||||
* String with that of the replace String.
|
||||
@@ -508,48 +453,27 @@ public class StringHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a String as an argument and removes all occurrences of
|
||||
* the supplied Char. It returns the resulting String.
|
||||
* Splits at any whitespace "," or ";" and trims the result.
|
||||
* It does not return empty entries.
|
||||
*/
|
||||
public static String removeChar(String s, char chr) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (c != chr) {
|
||||
sb.append(c);
|
||||
}
|
||||
public static String[] splitNames(String names) {
|
||||
if (names == null || names.isEmpty()) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a String as an argument and removes all occurrences of
|
||||
* the supplied Chars. It returns the resulting String.
|
||||
*/
|
||||
public static String removeChars(String s, char[] chr) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(s.length());
|
||||
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
if (!charMatch(c, chr)) {
|
||||
sb.append(c);
|
||||
}
|
||||
String[] result = SPLIT_NAMES.split(names);
|
||||
if (result.length == 0) {
|
||||
return EMPTY_STRING_ARRAY; // don't know if this ever can happen
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static boolean charMatch(int iChr, char[] chr) {
|
||||
for (char aChr : chr) {
|
||||
if (iChr == aChr) {
|
||||
return true;
|
||||
if ("".equals(result[0])) { // = input string starts with whitespace
|
||||
if (result.length == 1) { // = input string contains only whitespace
|
||||
return EMPTY_STRING_ARRAY;
|
||||
} else {
|
||||
String ret[] = new String[result.length-1]; // remove first entry
|
||||
System.arraycopy(result, 1, ret, 0, ret.length);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The results of bean cache hit.
|
||||
*/
|
||||
public class BeanCacheResult<T> {
|
||||
|
||||
private List<Entry<T>> list = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add an entry.
|
||||
*/
|
||||
public void add(T bean, Object key) {
|
||||
list.add(new Entry<>(bean, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hits.
|
||||
*/
|
||||
public List<Entry<T>> hits() {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean and cache key pair.
|
||||
*/
|
||||
static class Entry<T> {
|
||||
|
||||
private final T bean;
|
||||
private final Object key;
|
||||
|
||||
public Entry(T bean, Object key) {
|
||||
this.bean = bean;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key or id value.
|
||||
*/
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class BindParams implements Serializable {
|
||||
* Bind hash and count used to detect when the bind values have changed such
|
||||
* that the generated SQL (with named parameters) needs to be recalculated.
|
||||
*/
|
||||
private int[] bindHash;
|
||||
private String bindHash;
|
||||
|
||||
public BindParams() {
|
||||
}
|
||||
@@ -56,32 +56,29 @@ public class BindParams implements Serializable {
|
||||
* taken into account when calculating the query hash.
|
||||
* </p>
|
||||
*/
|
||||
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
|
||||
int[] vals = calcQueryPlanHash();
|
||||
builder.add(vals[0]).bind(vals[1]);
|
||||
public String calcQueryPlanHash() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
buildQueryPlanHash(builder);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and return a query plan bind hash with total bind count.
|
||||
*/
|
||||
public int[] calcQueryPlanHash() {
|
||||
public void buildQueryPlanHash(StringBuilder builder) {
|
||||
int tempBindCount;
|
||||
int bc = 0;
|
||||
int hc = 92821;
|
||||
for (Param param : positionedParameters) {
|
||||
tempBindCount = param.queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
builder.append("p").append(bc).append(" ?:").append(tempBindCount).append(",");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
|
||||
tempBindCount = entry.getValue().queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + entry.getKey().hashCode();
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
builder.append("n").append(bc).append(" k:").append(entry.getKey()).append(" ?:").append(tempBindCount).append(",");
|
||||
}
|
||||
|
||||
return new int[]{hc, bc};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,9 +274,9 @@ public class BindParams implements Serializable {
|
||||
bindHash = calcQueryPlanHash();
|
||||
return false;
|
||||
}
|
||||
int[] oldPlan = bindHash;
|
||||
String oldPlan = bindHash;
|
||||
bindHash = calcQueryPlanHash();
|
||||
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
|
||||
return bindHash.equals(oldPlan);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebean.plugin;
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
@@ -1,57 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* A hash for a query plan.
|
||||
*/
|
||||
public class HashQueryPlan {
|
||||
|
||||
private final String rawSql;
|
||||
|
||||
private final int planHash;
|
||||
|
||||
private final int bindCount;
|
||||
|
||||
public HashQueryPlan(String rawSql, int planHash, int bindCount) {
|
||||
this.rawSql = rawSql;
|
||||
this.planHash = planHash;
|
||||
this.bindCount = bindCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return planHash + ":" + bindCount + (rawSql != null ? ":r" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as a partial key. For rawSql hash the sql is part of the key and as such
|
||||
* needs to be included in order to have a complete key. Typically the MD5 of the sql
|
||||
* can be used as a shot form proxy for the actual sql.
|
||||
*/
|
||||
public String getPartialKey() {
|
||||
return planHash + "_" + bindCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = planHash;
|
||||
hc = hc * 92821 + bindCount;
|
||||
hc = hc * 92821 + (rawSql == null ? 0 : rawSql.hashCode());
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof HashQueryPlan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashQueryPlan e = (HashQueryPlan) obj;
|
||||
//noinspection StringEquality
|
||||
return e.planHash == planHash
|
||||
&& e.bindCount == bindCount
|
||||
&& ((e.rawSql == rawSql) || (e.rawSql != null && e.rawSql.equals(rawSql)));
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Used to build HashQueryPlan instances.
|
||||
*/
|
||||
public class HashQueryPlanBuilder {
|
||||
|
||||
private int planHash;
|
||||
|
||||
private int bindCount;
|
||||
|
||||
public HashQueryPlanBuilder() {
|
||||
this.planHash = 92821;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return planHash + ":" + bindCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a class to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(Class<?> cls) {
|
||||
planHash = planHash * 92821 + cls.getName().hashCode();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(Object object) {
|
||||
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the set with order being important.
|
||||
*/
|
||||
public HashQueryPlanBuilder addOrdered(Set<?> set) {
|
||||
if (set == null) {
|
||||
add(false);
|
||||
} else {
|
||||
add(true);
|
||||
for (Object o : set) {
|
||||
add(o);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an integer to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(int hashValue) {
|
||||
planHash = planHash * 92821 + (hashValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a boolean to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(boolean booleanValue) {
|
||||
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a number to the bind count for the hash.
|
||||
*/
|
||||
public void bind(int extraBindCount) {
|
||||
bindCount += extraBindCount;
|
||||
}
|
||||
|
||||
public void bindIfNotNull(Object someValue) {
|
||||
if (someValue != null) {
|
||||
bindCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the calculated HashQueryPlan.
|
||||
*/
|
||||
public String build() {
|
||||
return planHash + "_" + bindCount;
|
||||
}
|
||||
|
||||
public int getPlanHash() {
|
||||
return planHash;
|
||||
}
|
||||
|
||||
public int getBindCount() {
|
||||
return bindCount;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.TxScope;
|
||||
|
||||
/**
|
||||
@@ -10,28 +9,20 @@ import io.ebean.TxScope;
|
||||
public class HelpScopeTrans {
|
||||
|
||||
/**
|
||||
* Create a ScopeTrans for a given methods TxScope.
|
||||
* Entering an enhanced transactional method.
|
||||
*/
|
||||
public static ScopeTrans createScopeTrans(TxScope txScope) {
|
||||
|
||||
EbeanServer server = Ebean.getServer(txScope.getServerName());
|
||||
SpiEbeanServer iserver = (SpiEbeanServer) server;
|
||||
return iserver.createScopeTrans(txScope);
|
||||
public static void enter(TxScope txScope) {
|
||||
server().scopedTransactionEnter(txScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exiting the method in an expected fashion.
|
||||
* <p>
|
||||
* That is returning successfully or via a caught exception.
|
||||
* Unexpected exceptions are caught via the Thread uncaughtExceptionHandler.
|
||||
* </p>
|
||||
*
|
||||
* @param returnOrThrowable the return or throwable object
|
||||
* @param opCode the opcode for ATHROW or ARETURN etc
|
||||
* @param scopeTrans the scoped transaction the method was run with.
|
||||
* Exiting an enhanced transactional method.
|
||||
*/
|
||||
public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans) {
|
||||
public static void exit(Object returnOrThrowable, int opCode) {
|
||||
server().scopedTransactionExit(returnOrThrowable, opCode);
|
||||
}
|
||||
|
||||
scopeTrans.onExit(returnOrThrowable, opCode);
|
||||
private static SpiEbeanServer server() {
|
||||
return (SpiEbeanServer) Ebean.getDefaultServer();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
idList.add(desc.getId(bean));
|
||||
}
|
||||
|
||||
if (!idList.isEmpty()) {
|
||||
|
||||
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
|
||||
int extraIds = batchSize - batch.size();
|
||||
if (extraIds > 0) {
|
||||
// for performance make up the Id's to the batch size
|
||||
@@ -156,9 +157,9 @@ public class LoadBeanRequest extends LoadRequest {
|
||||
for (Object aList : list) {
|
||||
EntityBean loadedBean = (EntityBean) aList;
|
||||
loadedIds.add(desc.getId(loadedBean));
|
||||
if (isLoadCache()) {
|
||||
desc.cacheBeanPut(loadedBean);
|
||||
}
|
||||
}
|
||||
if (isLoadCache()) {
|
||||
desc.cacheBeanPutAll(list);
|
||||
}
|
||||
|
||||
if (lazyLoadProperty != null) {
|
||||
|
||||
@@ -107,11 +107,13 @@ public class LoadManyRequest extends LoadRequest {
|
||||
for (BeanCollection<?> bc : batch) {
|
||||
idList.add(many.getParentId(bc.getOwnerBean()));
|
||||
}
|
||||
int extraIds = batchSize - batch.size();
|
||||
if (extraIds > 0) {
|
||||
Object firstId = idList.get(0);
|
||||
for (int i = 0; i < extraIds; i++) {
|
||||
idList.add(firstId);
|
||||
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
|
||||
int extraIds = batchSize - batch.size();
|
||||
if (extraIds > 0) {
|
||||
Object firstId = idList.get(0);
|
||||
for (int i = 0; i < extraIds; i++) {
|
||||
idList.add(firstId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,16 +26,6 @@ public abstract class LoadRequest {
|
||||
*/
|
||||
public abstract Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
|
||||
* true. This is for testing purposes to confirm the secondary query executes etc.
|
||||
*/
|
||||
public void logSecondaryQuery(SpiQuery<?> query) {
|
||||
if (parentRequest != null && parentRequest.isLogSecondaryQuery()) {
|
||||
parentRequest.getQuery().logSecondaryQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a lazy load and false if it is a secondary query.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
|
||||
import io.ebean.Pairs;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Natural key entry with name value pairs for each of the properties making up the key.
|
||||
*/
|
||||
public class NaturalKeyEntry {
|
||||
|
||||
private Map<String,Object> map = new HashMap<>();
|
||||
private Object key;
|
||||
private Object inValue;
|
||||
|
||||
/**
|
||||
* Used when query query just has a series of EQ expressions (no IN clause).
|
||||
*/
|
||||
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList) {
|
||||
load(eqList);
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create when query uses an IN clause.
|
||||
*/
|
||||
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList, String inProperty, Object inValue) {
|
||||
load(eqList);
|
||||
if (inProperty != null) {
|
||||
map.put(inProperty, inValue);
|
||||
this.inValue = inValue;
|
||||
}
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create when query uses an IN PAIRS clause.
|
||||
*/
|
||||
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList,
|
||||
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
|
||||
load(eqList);
|
||||
map.put(inMapProperty0, pair.getA());
|
||||
map.put(inMapProperty1, pair.getB());
|
||||
this.inValue = pair;
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
private void load(List<NaturalKeyEq> eqList) {
|
||||
if (eqList != null) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
map.put(eq.property, eq.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object calculateKey(String[] naturalKey) {
|
||||
|
||||
if (naturalKey.length == 1) {
|
||||
return map.get(naturalKey[0]);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
sb.append(map.get(key)).append(";");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural cache key (String concatenation of values).
|
||||
*/
|
||||
public Object key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inValue (used to remove from IN clause of original query).
|
||||
*/
|
||||
public Object getInValue() {
|
||||
return inValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* A property value pair in a natural key lookup.
|
||||
*/
|
||||
public class NaturalKeyEq {
|
||||
|
||||
final String property;
|
||||
final Object value;
|
||||
|
||||
public NaturalKeyEq(String property, Object value) {
|
||||
this.property = property;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user