mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.5.1</version>
|
||||
<version>11.9.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.5.1</tag>
|
||||
<tag>ebean-11.9.1</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -72,6 +72,17 @@
|
||||
</profiles>
|
||||
<dependencies>
|
||||
|
||||
<!--
|
||||
Projects are expected to explicit depend on version
|
||||
of slf4j that they want to use
|
||||
-->
|
||||
<dependency>
|
||||
<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
|
||||
@@ -92,19 +103,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>3.4</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.2</version>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -116,19 +121,13 @@
|
||||
<dependency>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>avaje-classpath-scanner</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<version>2.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>[1.7.1,1.7.99)</version>
|
||||
<version>11.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -205,6 +204,13 @@
|
||||
<!--<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>
|
||||
|
||||
@@ -1,55 +1,91 @@
|
||||
package io.ebean;
|
||||
|
||||
/**
|
||||
* Enum to control the different cache modes for queryCache (and maybe later) beanCache.
|
||||
*
|
||||
* If cache is enabled, you must be careful, what you do with the returned collection.
|
||||
* 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
|
||||
*
|
||||
* 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 cace (query & store the resut).
|
||||
* Use the cache and store a result when needed.
|
||||
*/
|
||||
ON(true, true),
|
||||
|
||||
ON(true, true),
|
||||
|
||||
/**
|
||||
* Do not read from cache, but write retrived value to cache.
|
||||
* Use this, if you want to get the fresh value from database and a CacheMode.ON query will follow.
|
||||
* 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>
|
||||
*/
|
||||
RECACHE(false, true),
|
||||
|
||||
AUTO(true, true),
|
||||
|
||||
/**
|
||||
* Query the cache for value. If it is there, use it, otherwise hit database but do NOT put the value
|
||||
* into the cache. (this mode is for completeness. There's probably no use case for this)
|
||||
* 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.
|
||||
*/
|
||||
QUERY_ONLY(true,false);
|
||||
|
||||
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;
|
||||
|
||||
private CacheMode(boolean get, boolean put) {
|
||||
CacheMode(boolean get, boolean put) {
|
||||
this.get = get;
|
||||
this.put = put;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retruns <code>true</code> if value is read from cache.
|
||||
* Return true if value is read from cache.
|
||||
*/
|
||||
public boolean isGet() {
|
||||
return get;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if value (from database) is written to cache.
|
||||
* Return true if a newly loaded value (from database) is put into the cache.
|
||||
*/
|
||||
public boolean isPut() {
|
||||
return put;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -851,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
|
||||
|
||||
@@ -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,9 +5,9 @@ import io.ebean.search.MultiMatch;
|
||||
import io.ebean.search.TextCommonTerms;
|
||||
import io.ebean.search.TextQueryString;
|
||||
import io.ebean.search.TextSimple;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Collection;
|
||||
@@ -261,6 +261,24 @@ public interface ExpressionList<T> {
|
||||
@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).
|
||||
@@ -439,14 +457,24 @@ 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 the {@link CacheMode} 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)
|
||||
*/
|
||||
@@ -793,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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -666,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
|
||||
*
|
||||
@@ -696,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
|
||||
*
|
||||
@@ -815,6 +813,21 @@ public interface Query<T> {
|
||||
@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).
|
||||
@@ -1292,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")
|
||||
@@ -1307,22 +1320,36 @@ 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 the {@link CacheMode} to use the query for executing this query.
|
||||
*/
|
||||
Query<T> setUseQueryCache(CacheMode useQueryCache);
|
||||
Query<T> setUseQueryCache(CacheMode queryCacheMode);
|
||||
|
||||
/**
|
||||
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
|
||||
@@ -1342,6 +1369,12 @@ public interface Query<T> {
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
@@ -1356,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);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public interface RawSqlBuilder {
|
||||
* resultSet.
|
||||
*/
|
||||
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return XServiceProvider.get().resultSet(resultSet, propertyNames);
|
||||
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +30,7 @@ public interface RawSqlBuilder {
|
||||
* this query.
|
||||
*/
|
||||
static RawSqlBuilder unparsed(String sql) {
|
||||
return XServiceProvider.get().unparsed(sql);
|
||||
return XServiceProvider.rawSql().unparsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ public interface RawSqlBuilder {
|
||||
* </p>
|
||||
*/
|
||||
static RawSqlBuilder parse(String sql) {
|
||||
return XServiceProvider.get().parsed(sql);
|
||||
return XServiceProvider.rawSql().parsed(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,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.
|
||||
@@ -42,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.
|
||||
*/
|
||||
|
||||
@@ -48,10 +48,16 @@ public final class TxScope {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -185,6 +191,51 @@ public final class TxScope {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebean.service.SpiRawSqlService;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -10,9 +11,11 @@ import java.util.ServiceLoader;
|
||||
*/
|
||||
class XServiceProvider {
|
||||
|
||||
private static SpiRawSqlService builder = init();
|
||||
private static SpiRawSqlService rawSqlService = initRawSql();
|
||||
|
||||
private static SpiRawSqlService init() {
|
||||
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
|
||||
|
||||
private static SpiRawSqlService initRawSql() {
|
||||
|
||||
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
@@ -21,10 +24,27 @@ class XServiceProvider {
|
||||
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 get() {
|
||||
return builder;
|
||||
static SpiRawSqlService rawSql() {
|
||||
return rawSqlService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the RawSqlService implementation.
|
||||
*/
|
||||
static SpiProfileLocationFactory profileLocationFactory() {
|
||||
return profileLocationFactory;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -13,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
-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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -273,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).
|
||||
*/
|
||||
@@ -1442,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.
|
||||
@@ -1458,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.
|
||||
*/
|
||||
@@ -2607,6 +2683,7 @@ public class ServerConfig {
|
||||
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);
|
||||
|
||||
@@ -16,5 +16,5 @@ public interface TenantDataSourceProvider {
|
||||
/**
|
||||
* Shutdown all the DataSources.
|
||||
*/
|
||||
default void shutdown(boolean deregisterDriver) {};
|
||||
default void shutdown(boolean deregisterDriver) {}
|
||||
}
|
||||
|
||||
@@ -61,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.
|
||||
*/
|
||||
@@ -227,7 +229,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -390,8 +392,6 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
@@ -399,17 +399,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;
|
||||
|
||||
@@ -83,6 +83,16 @@ public interface DbMigration {
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
@@ -101,7 +111,6 @@ public interface DbMigration {
|
||||
* <pre>{@code
|
||||
*
|
||||
* DbMigration migration = DbMigration.create();
|
||||
*
|
||||
* migration.setPlatform(Platform.POSTGRES);
|
||||
*
|
||||
* migration.generateMigration();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -157,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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Pairs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collects the data for processing the natural key cache processing.
|
||||
*/
|
||||
public class NaturalKeyQueryData<T> {
|
||||
|
||||
private final String[] naturalKey;
|
||||
|
||||
/**
|
||||
* Only one of IN or IN PAIRS is allowed.
|
||||
*/
|
||||
private boolean hasIn;
|
||||
|
||||
// IN Pairs clause - only one allowed
|
||||
private String inProperty0, inProperty1;
|
||||
private List<Pairs.Entry> inPairs;
|
||||
|
||||
// IN clause - only one allowed
|
||||
private Collection<?> inValues;
|
||||
private String inProperty;
|
||||
|
||||
// normal EQ expressions
|
||||
private List<NaturalKeyEq> eqList;
|
||||
|
||||
private NaturalKeySet set;
|
||||
|
||||
private int hitCount;
|
||||
|
||||
public NaturalKeyQueryData(String[] naturalKey) {
|
||||
this.naturalKey = naturalKey;
|
||||
}
|
||||
|
||||
private boolean matchProperty(String propName) {
|
||||
|
||||
for (String key : naturalKey) {
|
||||
if (key.equals(propName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for In Pairs expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchInPairs(Pairs pairs) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
}
|
||||
if (matchProperty(pairs.getProperty0()) && matchProperty(pairs.getProperty1())) {
|
||||
this.hasIn = true;
|
||||
this.inProperty0 = pairs.getProperty0();
|
||||
this.inProperty1 = pairs.getProperty1();
|
||||
this.inPairs = pairs.getEntries();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for IN expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchIn(String propName, Collection<?> sourceValues) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
}
|
||||
if (matchProperty(propName)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty = propName;
|
||||
this.inValues = sourceValues;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for an EQ expression.
|
||||
*/
|
||||
public boolean matchEq(String propName, Object bindValue) {
|
||||
if (matchProperty(propName)) {
|
||||
if (eqList == null) {
|
||||
eqList = new ArrayList<>();
|
||||
}
|
||||
eqList.add(new NaturalKeyEq(propName, bindValue));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the set of natural keys we will use.
|
||||
*/
|
||||
public NaturalKeySet buildKeys() {
|
||||
|
||||
if (!expressionCount() || !matchProperties()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.set = new NaturalKeySet();
|
||||
if (inValues != null) {
|
||||
// a findList() with an IN clause so we project
|
||||
// for every IN value a natural key combination
|
||||
for (Object inValue : inValues) {
|
||||
set.add(new NaturalKeyEntry(naturalKey, eqList, inProperty, inValue));
|
||||
}
|
||||
} else if (inPairs != null) {
|
||||
// a findList() with an IN Map clause so we project
|
||||
// for every IN value a natural key combination
|
||||
for (Pairs.Entry entry : inPairs) {
|
||||
set.add(new NaturalKeyEntry(naturalKey, eqList, inProperty0, inProperty1, entry));
|
||||
}
|
||||
|
||||
} else {
|
||||
// only one - a findOne()
|
||||
set.add(new NaturalKeyEntry(naturalKey, eqList));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the properties match the natural key properties.
|
||||
*/
|
||||
private boolean matchProperties() {
|
||||
if (naturalKey.length == 1) {
|
||||
// simple single property case
|
||||
if (inProperty != null) {
|
||||
return inProperty.equals(naturalKey[0]);
|
||||
} else {
|
||||
return eqList.get(0).property.equals(naturalKey[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// multiple properties case
|
||||
Set<String> exprProps = new HashSet<>();
|
||||
if (inProperty != null) {
|
||||
exprProps.add(inProperty);
|
||||
}
|
||||
if (inProperty0 != null) {
|
||||
exprProps.add(inProperty0);
|
||||
}
|
||||
if (inProperty1 != null) {
|
||||
exprProps.add(inProperty1);
|
||||
}
|
||||
if (eqList != null) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
exprProps.add(eq.property);
|
||||
}
|
||||
}
|
||||
if (exprProps.size() != naturalKey.length) {
|
||||
return false;
|
||||
}
|
||||
for (String key : naturalKey) {
|
||||
if (!exprProps.remove(key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return exprProps.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all the natural key properties are defined.
|
||||
*/
|
||||
private boolean expressionCount() {
|
||||
|
||||
int defined = (inValues == null) ? 0 : 1;
|
||||
defined += (inPairs == null) ? 0 : 2;
|
||||
defined += (eqList == null) ? 0 : eqList.size();
|
||||
return defined == naturalKey.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of entries in the IN clause left remaining (to hit the DB with).
|
||||
*/
|
||||
public boolean allHits() {
|
||||
return hitCount > 0
|
||||
&& hitCount == set.size()
|
||||
&& (inValues == null || inValues.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the IN clause removing the hit entry.
|
||||
*/
|
||||
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
|
||||
|
||||
List<BeanCacheResult.Entry<T>> hits = cacheResult.hits();
|
||||
this.hitCount = hits.size();
|
||||
|
||||
List<T> beans = new ArrayList<>(hitCount);
|
||||
|
||||
for (BeanCacheResult.Entry<T> hit : hits) {
|
||||
if (inValues != null) {
|
||||
Object naturalKey = hit.getKey();
|
||||
Object inValue = set.getInValue(naturalKey);
|
||||
inValues.remove(inValue);
|
||||
|
||||
} else if (inPairs != null) {
|
||||
Object naturalKey = hit.getKey();
|
||||
Pairs.Entry inValue = (Pairs.Entry)set.getInValue(naturalKey);
|
||||
inPairs.remove(inValue);
|
||||
}
|
||||
beans.add(hit.getBean());
|
||||
}
|
||||
|
||||
return beans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class NaturalKeySet {
|
||||
|
||||
|
||||
private final Map<Object, NaturalKeyEntry> map = new LinkedHashMap<>();
|
||||
|
||||
public NaturalKeySet() {
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public void add(NaturalKeyEntry entry) {
|
||||
map.put(entry.key(), entry);
|
||||
}
|
||||
|
||||
public Set<Object> keys() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
public Object getInValue(Object naturalKey) {
|
||||
NaturalKeyEntry entry = map.get(naturalKey);
|
||||
return entry.getInValue();
|
||||
}
|
||||
}
|
||||
@@ -97,4 +97,9 @@ public interface SpiExpression extends Expression {
|
||||
* Return the bind Id value if this is a "equal to" expression for the id property.
|
||||
*/
|
||||
Object getIdEqualTo(String idName);
|
||||
|
||||
/**
|
||||
* Check for match to a natural key query returning false if it doesn't match.
|
||||
*/
|
||||
boolean naturalKey(NaturalKeyQueryData data);
|
||||
}
|
||||
|
||||
@@ -74,5 +74,5 @@ public interface SpiExpressionRequest {
|
||||
/**
|
||||
* Append IN expression taking into account platform and type support for Multi-value.
|
||||
*/
|
||||
void appendInExpression(boolean not, Object[] bindValues);
|
||||
void appendInExpression(boolean not, List<Object> bindValues);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.EbeanServer;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
@@ -98,17 +99,30 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Delete query.
|
||||
*/
|
||||
DELETE(FIND_DELETE),
|
||||
DELETE(FIND_DELETE, true),
|
||||
|
||||
/**
|
||||
* Update query.
|
||||
*/
|
||||
UPDATE(FIND_UPDATE);
|
||||
UPDATE(FIND_UPDATE, true);
|
||||
|
||||
boolean update;
|
||||
String profileEventId;
|
||||
|
||||
Type(String profileEventId) {
|
||||
this(profileEventId, false);
|
||||
}
|
||||
|
||||
Type(String profileEventId, boolean update) {
|
||||
this.profileEventId = profileEventId;
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an Update or Delete query (not read only).
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public String profileEventId() {
|
||||
@@ -161,9 +175,19 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
short getProfileId();
|
||||
|
||||
/**
|
||||
* Check for a single "equal to" expression for the Id.
|
||||
* Return the profile location for this query.
|
||||
*/
|
||||
void checkIdEqualTo();
|
||||
ProfileLocation getProfileLocation();
|
||||
|
||||
/**
|
||||
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
|
||||
*/
|
||||
boolean isFindById();
|
||||
|
||||
/**
|
||||
* Return true if this is a "find all" query. Used to set a "find all" profile location if necessary.
|
||||
*/
|
||||
boolean isFindAll();
|
||||
|
||||
/**
|
||||
* Return true if AutoTune should be attempted on this query.
|
||||
@@ -366,6 +390,17 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
ManyWhereJoins getManyWhereJoins();
|
||||
|
||||
/**
|
||||
* Reset AUTO mode to OFF for findList(). Expect explicit cache use with findList().
|
||||
*/
|
||||
void resetBeanCacheAutoMode(boolean findOne);
|
||||
|
||||
/**
|
||||
* Collect natural key data for this query or null if the query does not match
|
||||
* the requirements of natural key lookup.
|
||||
*/
|
||||
NaturalKeyQueryData<T> naturalKey();
|
||||
|
||||
/**
|
||||
* Return a Natural Key bind parameter if supported by this query.
|
||||
*/
|
||||
@@ -579,27 +614,30 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
boolean hasMaxRowsOrFirstRow();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache should be exclude for query or lazy loading.
|
||||
* Return true if the query should GET against bean cache.
|
||||
*/
|
||||
boolean isExcludeBeanCache();
|
||||
boolean isBeanCacheGet();
|
||||
|
||||
/**
|
||||
* Return true if this query should use the bean cache.
|
||||
* It is not skipped and bean caching is supported.
|
||||
* Return true if the query should PUT against the bean cache.
|
||||
*/
|
||||
boolean isUseBeanCache();
|
||||
boolean isBeanCachePut();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
|
||||
*/
|
||||
boolean isBeanCacheReload();
|
||||
|
||||
/**
|
||||
* Return the cache mode for using the bean cache (Get and Put).
|
||||
*/
|
||||
CacheMode getUseBeanCache();
|
||||
|
||||
/**
|
||||
* Return the cache mode if this query should use/check the query cache.
|
||||
*/
|
||||
CacheMode getUseQueryCache();
|
||||
|
||||
/**
|
||||
* Return true if the beans from this query should be loaded into the bean
|
||||
* cache.
|
||||
*/
|
||||
boolean isLoadBeanCache();
|
||||
|
||||
/**
|
||||
* Return true if the beans returned by this query should be read only.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
@@ -24,6 +25,11 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public interface SpiTransaction extends Transaction {
|
||||
|
||||
/**
|
||||
* Return the user defined label for the transaction.
|
||||
*/
|
||||
String getLabel();
|
||||
|
||||
/**
|
||||
* Return the string prefix with the transaction id and label used in logging.
|
||||
*/
|
||||
@@ -298,4 +304,14 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return the stream that profiling events are written to.
|
||||
*/
|
||||
ProfileStream profileStream();
|
||||
|
||||
/**
|
||||
* Set the profile location for this transaction.
|
||||
*/
|
||||
void setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Return the profile location for this transaction.
|
||||
*/
|
||||
ProfileLocation getProfileLocation();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
@@ -29,6 +30,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.translate(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLabel(String label) {
|
||||
transaction.setLabel(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return transaction.getLabel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitAndContinue() {
|
||||
transaction.commitAndContinue();
|
||||
@@ -54,6 +65,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
return transaction.profileStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProfileLocation(ProfileLocation profileLocation) {
|
||||
transaction.setProfileLocation(profileLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return transaction.getProfileLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTenantId(Object tenantId) {
|
||||
transaction.setTenantId(tenantId);
|
||||
|
||||
@@ -2,21 +2,22 @@ package io.ebeaninternal.dbmigration;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.dbmigration.DbMigration;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.config.DbConstraintNaming;
|
||||
import io.ebean.config.DbMigrationConfig;
|
||||
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.db2.DB2Platform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
|
||||
import io.ebean.config.dbplatform.oracle.OraclePlatform;
|
||||
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
|
||||
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
|
||||
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
|
||||
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
|
||||
import io.ebean.dbmigration.DbMigration;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.migration.Migration;
|
||||
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlWriter;
|
||||
@@ -27,7 +28,6 @@ import io.ebeaninternal.dbmigration.model.MigrationVersion;
|
||||
import io.ebeaninternal.dbmigration.model.ModelContainer;
|
||||
import io.ebeaninternal.dbmigration.model.ModelDiff;
|
||||
import io.ebeaninternal.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;
|
||||
@@ -53,7 +53,7 @@ import java.util.List;
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DefaultDbMigration migration = new DefaultDbMigration();
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
*
|
||||
@@ -88,6 +88,8 @@ public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
protected DbConstraintNaming constraintNaming;
|
||||
|
||||
protected Boolean strictMode;
|
||||
|
||||
/**
|
||||
* Create for offline migration generation.
|
||||
*/
|
||||
@@ -139,6 +141,11 @@ public class DefaultDbMigration implements DbMigration {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStrictMode(boolean strictMode) {
|
||||
this.strictMode = strictMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific platform to generate DDL for.
|
||||
* <p>
|
||||
@@ -184,7 +191,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* <h3>Example: Run for a single specific platform</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DefaultDbMigration migration = new DefaultDbMigration();
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
* migration.setPlatform(DbPlatformName.ORACLE);
|
||||
*
|
||||
@@ -195,7 +202,7 @@ public class DefaultDbMigration implements DbMigration {
|
||||
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* DefaultDbMigration migration = new DefaultDbMigration();
|
||||
* DbMigration migration = DbMigration.create();
|
||||
* migration.setPathToResources("src/main/resources");
|
||||
*
|
||||
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
|
||||
@@ -486,6 +493,9 @@ public class DefaultDbMigration implements DbMigration {
|
||||
databasePlatform = server.getDatabasePlatform();
|
||||
logger.debug("set platform to {}", databasePlatform.getName());
|
||||
}
|
||||
if (strictMode != null && migrationConfig != null) {
|
||||
migrationConfig.setStrictMode(strictMode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-17
@@ -5,6 +5,7 @@ import io.ebean.config.NamingConvention;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.config.dbplatform.IdType;
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.TableDdl;
|
||||
@@ -24,7 +25,6 @@ import io.ebeaninternal.dbmigration.migration.DropTable;
|
||||
import io.ebeaninternal.dbmigration.migration.ForeignKey;
|
||||
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
import io.ebean.util.StringHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
@@ -74,7 +74,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
*/
|
||||
protected Map<String, HistoryTableUpdate> regenerateHistoryTriggers = new LinkedHashMap<>();
|
||||
|
||||
private boolean strict;
|
||||
private boolean strictMode;
|
||||
|
||||
private final boolean sql2011History;
|
||||
|
||||
@@ -91,25 +91,24 @@ public class BaseTableDdl implements TableDdl {
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when adding a NEW column.
|
||||
*/
|
||||
DdlMigrationHelp(String tableName, Column column) throws IOException {
|
||||
DdlMigrationHelp(String tableName, Column column) {
|
||||
this.tableName = tableName;
|
||||
this.columnName = column.getName();
|
||||
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
|
||||
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
|
||||
|
||||
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
|
||||
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
|
||||
handleStrictError(tableName, columnName);
|
||||
}
|
||||
|
||||
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
|
||||
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for DdlMigrationHelp when altering a column.
|
||||
*/
|
||||
DdlMigrationHelp(AlterColumn alter) throws IOException {
|
||||
DdlMigrationHelp(AlterColumn alter) {
|
||||
this.tableName = alter.getTableName();
|
||||
this.columnName = alter.getColumnName();
|
||||
|
||||
@@ -120,18 +119,16 @@ public class BaseTableDdl implements TableDdl {
|
||||
// here we add the platform's default update script
|
||||
if (alter.getBefore().isEmpty() && alterNotNull) {
|
||||
if (defaultValue == null) {
|
||||
handleStrictError("non-null column has no default value: " + tableName + "." + columnName);
|
||||
handleStrictError(tableName, columnName);
|
||||
}
|
||||
before = Arrays.asList(platformDdl.getUpdateNullWithDefault());
|
||||
} else {
|
||||
before = getScriptsForPlatform(alter.getBefore(), platformDdl.getPlatform().getName());
|
||||
}
|
||||
|
||||
after = getScriptsForPlatform(alter.getAfter(), platformDdl.getPlatform().getName());
|
||||
|
||||
}
|
||||
|
||||
public void writeBefore(DdlBuffer buffer) throws IOException {
|
||||
void writeBefore(DdlBuffer buffer) throws IOException {
|
||||
if (!before.isEmpty()) {
|
||||
buffer.end();
|
||||
}
|
||||
@@ -141,8 +138,8 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
// here we run postmigration scripts
|
||||
void writeAfter(DdlBuffer buffer) throws IOException {
|
||||
// here we run post migration scripts
|
||||
for (String ddlScript : after) {
|
||||
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
|
||||
buffer.endOfStatement();
|
||||
@@ -175,11 +172,10 @@ public class BaseTableDdl implements TableDdl {
|
||||
return StringHelper.replaceString(ret, "${default}", defaultValue);
|
||||
}
|
||||
|
||||
private void handleStrictError(String message) {
|
||||
if (strict) {
|
||||
private void handleStrictError(String tableName, String columnName) {
|
||||
if (strictMode) {
|
||||
String message = "DB Migration of non-null column with no default value specified for: " + tableName + "." + columnName+" Use @DbDefault to specify a default value or specify dbMigration.setStrictMode(false)";
|
||||
throw new IllegalArgumentException(message);
|
||||
} else {
|
||||
System.err.println("Error in DDL: " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +194,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
this.historyTableSuffix = serverConfig.getHistoryTableSuffix();
|
||||
this.platformDdl = platformDdl;
|
||||
this.platformDdl.configure(serverConfig);
|
||||
this.strict = true; // TODO RPr serverConfig.getMigrationConfig().isStrict();
|
||||
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
|
||||
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
|
||||
this.sql2011History = hist != null && hist.isStandardsBased();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
* Return true if this is a "repeatable" version.
|
||||
*/
|
||||
public boolean isRepeatable() {
|
||||
// Clarification: The comparison here is intended to compare object references and not the content of the arrays.
|
||||
// This is a kind of shortcut to see if some configuration is in place that results in the use of something other than the default.
|
||||
return ordering == REPEAT_ORDERING;
|
||||
}
|
||||
|
||||
@@ -110,6 +112,8 @@ public class MigrationVersion implements Comparable<MigrationVersion> {
|
||||
*/
|
||||
private String formattedVersion(boolean normalised, boolean nextVersion) {
|
||||
|
||||
// Clarification: The comparison here is intended to compare object references and not the content of the arrays.
|
||||
// This is a kind of shortcut to see if some configuration is in place that results in the use of something other than the default.
|
||||
if (ordering == REPEAT_ORDERING) {
|
||||
return "R";
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Modify aware wrapper of a list.
|
||||
@@ -32,6 +33,19 @@ public class ModifyAwareList<E> implements List<E>, ModifyAwareOwner {
|
||||
return list.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ModifyAwareList)) return false;
|
||||
ModifyAwareList<?> that = (ModifyAwareList<?>) o;
|
||||
return Objects.equals(list, that.list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
return owner.isMarkedDirty();
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.ebeaninternal.json;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -19,14 +20,14 @@ public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
|
||||
*/
|
||||
final Map<K, V> map;
|
||||
|
||||
public ModifyAwareMap(Map<K, V> underyling) {
|
||||
this.map = underyling;
|
||||
public ModifyAwareMap(Map<K, V> underlying) {
|
||||
this.map = underlying;
|
||||
this.owner = new ModifyAwareFlag();
|
||||
}
|
||||
|
||||
public ModifyAwareMap(ModifyAwareOwner owner, Map<K, V> underyling) {
|
||||
public ModifyAwareMap(ModifyAwareOwner owner, Map<K, V> underlying) {
|
||||
this.owner = owner;
|
||||
this.map = underyling;
|
||||
this.map = underlying;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -34,6 +35,19 @@ public class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareOwner {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ModifyAwareMap)) return false;
|
||||
ModifyAwareMap<?, ?> that = (ModifyAwareMap<?, ?>) o;
|
||||
return Objects.equals(map, that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
return owner.isMarkedDirty();
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.json;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,19 @@ public class ModifyAwareSet<E> implements Set<E>, ModifyAwareOwner {
|
||||
return set.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ModifyAwareSet)) return false;
|
||||
ModifyAwareSet<?> that = (ModifyAwareSet<?>) o;
|
||||
return Objects.equals(set, that.set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(set);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E o) {
|
||||
if (set.add(o)) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
/**
|
||||
* Factory to create timed metric counters.
|
||||
*/
|
||||
public interface MetricFactory {
|
||||
|
||||
/**
|
||||
* Return the factory instance.
|
||||
*/
|
||||
static MetricFactory get() {
|
||||
return MetricServiceProvider.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a timed metric group.
|
||||
*/
|
||||
TimedMetricMap createTimedMetricMap(String name);
|
||||
|
||||
/**
|
||||
* Create a Timed metric.
|
||||
*/
|
||||
TimedMetric createTimedMetric(String name);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebeaninternal.server.profile.DMetricFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* Lookup MetricFactory service.
|
||||
*/
|
||||
class MetricServiceProvider {
|
||||
|
||||
private static MetricFactory metricFactory = init();
|
||||
|
||||
private static MetricFactory init() {
|
||||
|
||||
Iterator<MetricFactory> loader = ServiceLoader.load(MetricFactory.class).iterator();
|
||||
if (loader.hasNext()) {
|
||||
return loader.next();
|
||||
}
|
||||
return new DMetricFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MetricFactory implementation.
|
||||
*/
|
||||
static MetricFactory get() {
|
||||
return metricFactory;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Metric for timed events like transaction execution times.
|
||||
*/
|
||||
public interface TimedMetric {
|
||||
|
||||
/**
|
||||
* Add a time event (usually in microseconds).
|
||||
*/
|
||||
void add(long value);
|
||||
|
||||
/**
|
||||
* Return true if there are no metrics collected since the last collection.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Collect the timed metric statistics.
|
||||
*/
|
||||
TimedMetricStats collect(boolean reset);
|
||||
|
||||
/**
|
||||
* Add non empty metrics to the result.
|
||||
*/
|
||||
void collect(boolean reset, List<MetaTimedMetric> result);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A map of timed metrics keyed by a string.
|
||||
*/
|
||||
public interface TimedMetricMap {
|
||||
|
||||
/**
|
||||
* Add an execution for the given key.
|
||||
*/
|
||||
void add(String key, long exeMicros);
|
||||
|
||||
/**
|
||||
* Add non empty metrics to the given result.
|
||||
*/
|
||||
void collect(boolean reset, List<MetaTimedMetric> result);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
/**
|
||||
* Extend public MetaTimedMetric with ability to set details from profile location.
|
||||
*/
|
||||
public interface TimedMetricStats extends MetaTimedMetric {
|
||||
|
||||
/**
|
||||
* Additionally set the location.
|
||||
*/
|
||||
void setLocation(String location);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
|
||||
default:
|
||||
// not using autoTune when explicitly loading the l2 bean cache
|
||||
// or when using Versions query
|
||||
return !query.isLoadBeanCache() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,8 +122,7 @@ public class CacheChangeSet {
|
||||
*/
|
||||
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
|
||||
ManyKey key = new ManyKey(desc, manyProperty);
|
||||
ManyChange manyChange = manyChangeMap.computeIfAbsent(key, ManyChange::new);
|
||||
return manyChange;
|
||||
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.cluster.ClusterManager;
|
||||
|
||||
/**
|
||||
* Configuration options when creating the default cache manager.
|
||||
*/
|
||||
public class CacheManagerOptions {
|
||||
|
||||
private final ClusterManager clusterManager;
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private final boolean localL2Caching;
|
||||
|
||||
private CurrentTenantProvider currentTenantProvider;
|
||||
|
||||
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
|
||||
private ServerCacheOptions beanDefault = new ServerCacheOptions();
|
||||
private ServerCacheOptions queryDefault = new ServerCacheOptions();
|
||||
|
||||
CacheManagerOptions() {
|
||||
this.localL2Caching = true;
|
||||
this.clusterManager = null;
|
||||
this.serverConfig = null;
|
||||
this.cacheFactory = new DefaultServerCacheFactory();
|
||||
this.beanDefault = new ServerCacheOptions();
|
||||
this.queryDefault = new ServerCacheOptions();
|
||||
}
|
||||
|
||||
public CacheManagerOptions(ClusterManager clusterManager, ServerConfig serverConfig, boolean localL2Caching) {
|
||||
this.clusterManager = clusterManager;
|
||||
this.serverConfig = serverConfig;
|
||||
this.localL2Caching = localL2Caching;
|
||||
this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
|
||||
}
|
||||
|
||||
public CacheManagerOptions with(ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
|
||||
this.beanDefault = beanDefault;
|
||||
this.queryDefault = queryDefault;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CacheManagerOptions with(ServerCacheFactory cacheFactory) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CacheManagerOptions with(CurrentTenantProvider currentTenantProvider) {
|
||||
this.currentTenantProvider = currentTenantProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return (serverConfig == null) ? "db" : serverConfig.getName();
|
||||
}
|
||||
|
||||
public boolean isLocalL2Caching() {
|
||||
return localL2Caching;
|
||||
}
|
||||
|
||||
public ServerCacheFactory getCacheFactory() {
|
||||
return cacheFactory;
|
||||
}
|
||||
|
||||
public ServerCacheOptions getBeanDefault() {
|
||||
return beanDefault;
|
||||
}
|
||||
|
||||
public ServerCacheOptions getQueryDefault() {
|
||||
return queryDefault;
|
||||
}
|
||||
|
||||
public CurrentTenantProvider getCurrentTenantProvider() {
|
||||
return currentTenantProvider;
|
||||
}
|
||||
|
||||
public ClusterManager getClusterManager() {
|
||||
return clusterManager;
|
||||
}
|
||||
}
|
||||
@@ -44,12 +44,21 @@ public class DefaultCacheAdapter implements ServerCacheManager {
|
||||
|
||||
@Override
|
||||
public void clear(Class<?> beanType) {
|
||||
cacheManager.getBeanCache(beanType).clear();
|
||||
cacheManager.getQueryCache(beanType).clear();
|
||||
cacheManager.clear(beanType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAll() {
|
||||
cacheManager.clearAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllLocal() {
|
||||
cacheManager.clearAllLocal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLocal(Class<?> beanType) {
|
||||
cacheManager.clearLocal(beanType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,24 @@ import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
|
||||
/**
|
||||
* Manages the construction of caches.
|
||||
*/
|
||||
class DefaultCacheHolder {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger("io.ebean.cache.ALL");
|
||||
|
||||
private final ConcurrentHashMap<String, ServerCache> allCaches = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentHashMap<String, Set<String>> collectIdCaches = new ConcurrentHashMap<>();
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private final ServerCacheOptions beanDefault;
|
||||
@@ -25,6 +33,10 @@ class DefaultCacheHolder {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
DefaultCacheHolder(CacheManagerOptions builder) {
|
||||
this(builder.getCacheFactory(), builder.getBeanDefault(), builder.getQueryDefault(), builder.getCurrentTenantProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
@@ -42,7 +54,6 @@ class DefaultCacheHolder {
|
||||
ServerCache getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
|
||||
|
||||
return getCacheInternal(beanType, cacheKey, type);
|
||||
|
||||
}
|
||||
|
||||
private String key(String cacheKey, ServerCacheType type) {
|
||||
@@ -60,16 +71,43 @@ class DefaultCacheHolder {
|
||||
|
||||
private ServerCache createCache(Class<?> beanType, ServerCacheType type, String key) {
|
||||
ServerCacheOptions options = getCacheOptions(beanType, type);
|
||||
|
||||
if (type == ServerCacheType.COLLECTION_IDS) {
|
||||
synchronized (this) {
|
||||
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
|
||||
}
|
||||
}
|
||||
return cacheFactory.createCache(type, key, tenantProvider, options);
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
log.debug("clearAll");
|
||||
for (ServerCache serverCache : allCaches.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void clear(String name) {
|
||||
log.debug("clear {}", name);
|
||||
clearIfExists(key(name, ServerCacheType.QUERY));
|
||||
clearIfExists(key(name, ServerCacheType.BEAN));
|
||||
clearIfExists(key(name, ServerCacheType.NATURAL_KEY));
|
||||
Set<String> keys = collectIdCaches.get(name);
|
||||
if (keys != null) {
|
||||
for (String collectionIdKey : keys) {
|
||||
clearIfExists(collectionIdKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearIfExists(String fullKey) {
|
||||
ServerCache cache = allCaches.get(fullKey);
|
||||
if (cache != null) {
|
||||
log.trace("clear cache {}", fullKey);
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
|
||||
@@ -6,15 +6,14 @@ import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import io.ebean.cache.TenantAwareKey;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -217,20 +216,23 @@ public class DefaultServerCache implements ServerCache {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<Object, Object> keyValues) {
|
||||
keyValues.forEach(this::put);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value into the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object put(Object id, Object value) {
|
||||
public void put(Object id, Object value) {
|
||||
|
||||
Object key = key(id);
|
||||
CacheEntry entry = map.put(key, new CacheEntry(key, value));
|
||||
if (entry == null) {
|
||||
insertCount.increment();
|
||||
return null;
|
||||
} else {
|
||||
updateCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,14 +240,11 @@ public class DefaultServerCache implements ServerCache {
|
||||
* Remove an entry from the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object id) {
|
||||
public void remove(Object id) {
|
||||
|
||||
CacheEntry entry = map.remove(key(id));
|
||||
if (entry == null) {
|
||||
return null;
|
||||
} else {
|
||||
if (entry != null) {
|
||||
removeCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,10 +358,8 @@ public class DefaultServerCache implements ServerCache {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public int compare(CacheEntry entry1, CacheEntry entry2) {
|
||||
long x = entry1.getLastAccessTime();
|
||||
long y = entry2.getLastAccessTime();
|
||||
return (x < y) ? -1 : ((x == y) ? 0 : 1);
|
||||
public int compare(CacheEntry e1, CacheEntry e2) {
|
||||
return Long.compare(e1.getLastAccessTime(), e2.getLastAccessTime());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-8
@@ -1,34 +1,37 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebeaninternal.server.cluster.ClusterManager;
|
||||
|
||||
/**
|
||||
* Manages the bean and query caches.
|
||||
*/
|
||||
public class DefaultServerCacheManager implements SpiCacheManager {
|
||||
|
||||
private final ClusterManager clusterManager;
|
||||
|
||||
private final DefaultCacheHolder cacheHolder;
|
||||
|
||||
private final boolean localL2Caching;
|
||||
|
||||
private final String serverName;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(boolean localL2Caching, CurrentTenantProvider tenantProvider, ServerCacheFactory cacheFactory,
|
||||
ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
|
||||
this.localL2Caching = localL2Caching;
|
||||
this.cacheHolder = new DefaultCacheHolder(cacheFactory, beanDefault, queryDefault, tenantProvider);
|
||||
public DefaultServerCacheManager(CacheManagerOptions builder) {
|
||||
this.clusterManager = builder.getClusterManager();
|
||||
this.serverName = builder.getServerName();
|
||||
this.localL2Caching = builder.isLocalL2Caching();
|
||||
this.cacheHolder = new DefaultCacheHolder(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct when l2 cache is disabled.
|
||||
*/
|
||||
public DefaultServerCacheManager() {
|
||||
this(true, null, new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
|
||||
this(new CacheManagerOptions());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,6 +45,27 @@ public class DefaultServerCacheManager implements SpiCacheManager {
|
||||
@Override
|
||||
public void clearAll() {
|
||||
cacheHolder.clearAll();
|
||||
if (clusterManager != null) {
|
||||
clusterManager.cacheClearAll(serverName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllLocal() {
|
||||
cacheHolder.clearAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Class<?> beanType) {
|
||||
cacheHolder.clear(name(beanType));
|
||||
if (clusterManager != null) {
|
||||
clusterManager.cacheClear(serverName, beanType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLocal(Class<?> beanType) {
|
||||
cacheHolder.clear(name(beanType));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebeaninternal.server.cluster.BinaryMessage;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessageList;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Cache events broadcast across the cluster.
|
||||
*/
|
||||
public class RemoteCacheEvent {
|
||||
|
||||
private boolean clearAll;
|
||||
|
||||
private List<String> clearCaches;
|
||||
|
||||
/**
|
||||
* Clear all the caches.
|
||||
*/
|
||||
public RemoteCacheEvent(boolean clearAll) {
|
||||
this.clearAll = clearAll;
|
||||
this.clearCaches = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear caches for the given bean type.
|
||||
*/
|
||||
public RemoteCacheEvent(Class<?> beanType) {
|
||||
this.clearAll = false;
|
||||
this.clearCaches = new ArrayList<>(1);
|
||||
this.clearCaches.add(beanType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with the read options.
|
||||
*/
|
||||
public RemoteCacheEvent(boolean clearAll, List<String> beanTypes) {
|
||||
this.clearAll = clearAll;
|
||||
this.clearCaches = beanTypes;
|
||||
}
|
||||
|
||||
public boolean isClearAll() {
|
||||
return clearAll;
|
||||
}
|
||||
|
||||
public List<String> getClearCaches() {
|
||||
return clearCaches;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "clearAll:" + clearAll + " caches:" + clearCaches;
|
||||
}
|
||||
|
||||
public static RemoteCacheEvent readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
|
||||
boolean clearAll = dataInput.readBoolean();
|
||||
int size = dataInput.readInt();
|
||||
|
||||
List<String> clearCache = null;
|
||||
if (size > 0) {
|
||||
clearCache = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
clearCache.add(dataInput.readUTF());
|
||||
}
|
||||
}
|
||||
|
||||
return new RemoteCacheEvent(clearAll, clearCache);
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
int bufferSize = (clearCaches == null) ? 0 : clearCaches.size() * 30;
|
||||
|
||||
BinaryMessage msg = new BinaryMessage(bufferSize + 10);
|
||||
DataOutputStream os = msg.getOs();
|
||||
os.writeInt(BinaryMessage.TYPE_CACHE);
|
||||
os.writeBoolean(clearAll);
|
||||
if (clearCaches == null) {
|
||||
os.writeInt(0);
|
||||
} else {
|
||||
os.writeInt(clearCaches.size());
|
||||
for (String cacheName : clearCaches) {
|
||||
os.writeUTF(cacheName);
|
||||
}
|
||||
}
|
||||
msgList.add(msg);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -36,9 +36,24 @@ public interface SpiCacheManager {
|
||||
*/
|
||||
ServerCache getQueryCache(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Clear the caches for the given bean type.
|
||||
*/
|
||||
void clear(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Clear all the caches.
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* Clear all local caches.
|
||||
*/
|
||||
void clearAllLocal();
|
||||
|
||||
/**
|
||||
* Clear local caches for the given bean type.
|
||||
*/
|
||||
void clearLocal(Class<?> beanType);
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ public class BinaryMessage {
|
||||
public static final int TYPE_MSGCONTROL = 0;
|
||||
public static final int TYPE_BEANIUD = 1;
|
||||
public static final int TYPE_TABLEIUD = 2;
|
||||
public static final int TYPE_CACHE = 3;
|
||||
|
||||
public static final int TYPE_MSGACK = 8;
|
||||
public static final int TYPE_MSGRESEND = 9;
|
||||
|
||||
@@ -5,12 +5,10 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a List of BinaryMessage's.
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class BinaryMessageList {
|
||||
|
||||
final ArrayList<BinaryMessage> list = new ArrayList<>();
|
||||
final List<BinaryMessage> list = new ArrayList<>();
|
||||
|
||||
public void add(BinaryMessage msg) {
|
||||
list.add(msg);
|
||||
|
||||
@@ -24,5 +24,4 @@ public interface ClusterBroadcast {
|
||||
* Send a transaction event to all the members of the cluster.
|
||||
*/
|
||||
void broadcast(RemoteTransactionEvent remoteTransEvent);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import java.util.Properties;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
|
||||
/**
|
||||
* Factory to create the cluster broadcast service.
|
||||
@@ -10,5 +10,5 @@ public interface ClusterBroadcastFactory {
|
||||
/**
|
||||
* Create the cluster transport with the manager and deployment properties.
|
||||
*/
|
||||
ClusterBroadcast create(ClusterManager manager, Properties properties);
|
||||
ClusterBroadcast create(ClusterManager manager, ContainerConfig config);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ public class ClusterManager {
|
||||
|
||||
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
|
||||
|
||||
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
@@ -27,12 +25,14 @@ public class ClusterManager {
|
||||
|
||||
private boolean started;
|
||||
|
||||
private boolean shutdown;
|
||||
|
||||
public ClusterManager(ContainerConfig config) {
|
||||
if (!config.isClusterActive()) {
|
||||
broadcast = null;
|
||||
ClusterBroadcastFactory factory = createFactory();
|
||||
if (factory != null) {
|
||||
broadcast = factory.create(this, config);
|
||||
} else {
|
||||
ClusterBroadcastFactory factory = createFactory();
|
||||
broadcast = factory.create(this, config.getProperties());
|
||||
broadcast = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +47,6 @@ public class ClusterManager {
|
||||
if (iterator.hasNext()) {
|
||||
factory = iterator.next();
|
||||
}
|
||||
if (factory == null) {
|
||||
throw new IllegalStateException("No ClusterTransportFactory found in classpath. "
|
||||
+ " Probably need to add the avaje-ebeanorm-cluster dependency");
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
@@ -76,6 +72,24 @@ public class ClusterManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a cache clear all event to the cluster.
|
||||
*/
|
||||
public void cacheClearAll(String serverName) {
|
||||
if (broadcast != null) {
|
||||
broadcast.broadcast(new RemoteTransactionEvent(serverName).cacheClearAll());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a cache clear event to the cluster.
|
||||
*/
|
||||
public void cacheClear(String serverName, Class<?> beanType) {
|
||||
if (broadcast != null) {
|
||||
broadcast.broadcast(new RemoteTransactionEvent(serverName).cacheClear(beanType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if clustering is on.
|
||||
*/
|
||||
@@ -99,8 +113,8 @@ public class ClusterManager {
|
||||
* Shutdown the service and Deregister from the cluster.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (broadcast != null) {
|
||||
logger.info("ClusterManager shutdown ");
|
||||
if (broadcast != null && !shutdown) {
|
||||
shutdown = true;
|
||||
broadcast.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
/**
|
||||
* Not supported JSON or ARRAY expression handler.
|
||||
*/
|
||||
abstract class BaseDbExpression implements DbExpressionHandler {
|
||||
|
||||
private final String concatOperator;
|
||||
|
||||
BaseDbExpression(String concatOperator) {
|
||||
this.concatOperator = concatOperator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConcatOperator() {
|
||||
return concatOperator;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -6,7 +6,11 @@ import io.ebeaninternal.server.expression.Op;
|
||||
/**
|
||||
* Not supported JSON or ARRAY expression handler.
|
||||
*/
|
||||
public class NotSupportedDbExpression implements DbExpressionHandler {
|
||||
public class BasicDbExpression extends BaseDbExpression {
|
||||
|
||||
BasicDbExpression(String concatOperator) {
|
||||
super(concatOperator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
@@ -21,10 +21,14 @@ public class CObjectGraphNodeStatistics {
|
||||
|
||||
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
|
||||
|
||||
public CObjectGraphNodeStatistics(ObjectGraphNode node) {
|
||||
CObjectGraphNodeStatistics(ObjectGraphNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return count.sum() == 0;
|
||||
}
|
||||
|
||||
public void add(long beanCount, long exeMicros) {
|
||||
count.increment();
|
||||
totalTime.add(exeMicros);
|
||||
|
||||
@@ -15,7 +15,7 @@ public class CacheOptions {
|
||||
private final boolean enableBeanCache;
|
||||
private final boolean enableQueryCache;
|
||||
private final boolean readOnly;
|
||||
private final String naturalKey;
|
||||
private final String[] naturalKey;
|
||||
|
||||
/**
|
||||
* Construct for no caching.
|
||||
@@ -30,7 +30,7 @@ public class CacheOptions {
|
||||
/**
|
||||
* Construct with cache annotation.
|
||||
*/
|
||||
public CacheOptions(Cache cache, String naturalKey) {
|
||||
public CacheOptions(Cache cache, String[] naturalKey) {
|
||||
enableBeanCache = cache.enableBeanCache();
|
||||
enableQueryCache = cache.enableQueryCache();
|
||||
readOnly = cache.readOnly();
|
||||
@@ -61,7 +61,7 @@ public class CacheOptions {
|
||||
/**
|
||||
* Return the natural key property name.
|
||||
*/
|
||||
public String getNaturalKey() {
|
||||
public String[] getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ public interface CallStackFactory {
|
||||
/**
|
||||
* Create and return the CallStack given the stack trace elements.
|
||||
*/
|
||||
CallStack createCallStack(StackTraceElement[] finalTrace);
|
||||
CallStack createCallStack();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ import io.ebeaninternal.server.expression.Op;
|
||||
*/
|
||||
public interface DbExpressionHandler {
|
||||
|
||||
/**
|
||||
* Return the DB concat operator (Usually SQL standard "||").
|
||||
*/
|
||||
String getConcatOperator();
|
||||
|
||||
/**
|
||||
* Write the db platform specific json expression.
|
||||
*/
|
||||
|
||||
@@ -265,7 +265,7 @@ public class DefaultBeanLoader {
|
||||
if (desc.lazyLoadMany(ebi)) {
|
||||
return;
|
||||
}
|
||||
if (!draft && SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
|
||||
if (!draft && Mode.LAZYLOAD_BEAN == mode && desc.isBeanCaching()) {
|
||||
// lazy loading and the bean cache is active
|
||||
if (desc.cacheBeanLoad(bean, ebi, id, pc)) {
|
||||
return;
|
||||
@@ -292,7 +292,7 @@ public class DefaultBeanLoader {
|
||||
query.setMode(mode);
|
||||
query.setId(id);
|
||||
|
||||
if (embeddedOwnerIndex > -1 || mode.equals(SpiQuery.Mode.REFRESH_BEAN)) {
|
||||
if (embeddedOwnerIndex > -1 || mode == Mode.REFRESH_BEAN) {
|
||||
// make sure the query doesn't use the cache
|
||||
query.setUseCache(false);
|
||||
}
|
||||
@@ -301,7 +301,7 @@ public class DefaultBeanLoader {
|
||||
query.setReadOnly(true);
|
||||
}
|
||||
|
||||
if (SpiQuery.Mode.REFRESH_BEAN.equals(mode)) {
|
||||
if (Mode.REFRESH_BEAN == mode) {
|
||||
// explicitly state to load all properties on REFRESH.
|
||||
// Lobs default to fetch lazy so this forces lobs to be
|
||||
// included in a 'refresh' query
|
||||
|
||||
@@ -2,13 +2,56 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.bean.CallStack;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Default CallStackFactory where the Hash function for StackTraceElement includes the line number.
|
||||
*/
|
||||
public class DefaultCallStackFactory implements CallStackFactory {
|
||||
|
||||
private static final int IGNORE_LEADING_ELEMENTS = 5;
|
||||
|
||||
private static final String IO_EBEAN = "io.ebean";
|
||||
|
||||
private final int maxCallStack;
|
||||
|
||||
DefaultCallStackFactory(int maxCallStack) {
|
||||
this.maxCallStack = maxCallStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallStack createCallStack(StackTraceElement[] finalTrace) {
|
||||
public CallStack createCallStack() {
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
|
||||
// ignore the first 6 as they are always avaje stack elements
|
||||
int startIndex = IGNORE_LEADING_ELEMENTS;
|
||||
|
||||
// find the first non-avaje stackElement
|
||||
for (; startIndex < stackTrace.length; startIndex++) {
|
||||
if (!stackTrace[startIndex].getClassName().startsWith(IO_EBEAN)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int stackLength = stackTrace.length - startIndex;
|
||||
if (stackLength > maxCallStack) {
|
||||
// maximum of maxCallStack stackTrace elements
|
||||
stackLength = maxCallStack;
|
||||
}
|
||||
|
||||
// create the 'interesting' part of the stackTrace
|
||||
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
|
||||
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
|
||||
|
||||
if (stackLength < 1) {
|
||||
// this should not really happen
|
||||
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
|
||||
}
|
||||
|
||||
return createCallStack(finalTrace);
|
||||
}
|
||||
|
||||
private CallStack createCallStack(StackTraceElement[] finalTrace) {
|
||||
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
import io.ebean.config.PropertyMap;
|
||||
import io.ebean.config.ServerConfig;
|
||||
@@ -12,10 +11,12 @@ import io.ebean.config.TenantMode;
|
||||
import io.ebean.config.UnderscoreNamingConvention;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.h2.H2Platform;
|
||||
import io.ebeaninternal.dbmigration.DbOffline;
|
||||
import io.ebean.service.SpiContainer;
|
||||
import io.ebeaninternal.api.SpiBackgroundExecutor;
|
||||
import io.ebeaninternal.api.SpiContainerBootup;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.dbmigration.DbOffline;
|
||||
import io.ebeaninternal.server.cache.CacheManagerOptions;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
|
||||
import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
@@ -71,6 +72,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
clusterManager.shutdown();
|
||||
ShutdownManager.shutdown();
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
serverConfig.setDatabasePlatform(new H2Platform());
|
||||
} else {
|
||||
TenantMode tenantMode = serverConfig.getTenantMode();
|
||||
if (!TenantMode.DB.equals(tenantMode)) {
|
||||
if (TenantMode.DB != tenantMode) {
|
||||
setDataSource(serverConfig);
|
||||
if (!tenantMode.isDynamicDataSource()) {
|
||||
// check the autoCommit and Transaction Isolation
|
||||
@@ -201,7 +203,11 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
|
||||
ServerCacheFactory factory = plugin.create(serverConfig, executor);
|
||||
return new DefaultServerCacheManager(localL2Caching, serverConfig.getCurrentTenantProvider(), factory, beanOptions, queryOptions);
|
||||
|
||||
CacheManagerOptions builder = new CacheManagerOptions(clusterManager, serverConfig, localL2Caching)
|
||||
.with(beanOptions, queryOptions)
|
||||
.with(factory);
|
||||
return new DefaultServerCacheManager(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,18 +278,21 @@ public class DefaultContainer implements SpiContainer {
|
||||
*/
|
||||
private void setDataSource(ServerConfig config) {
|
||||
if (config.getDataSource() == null) {
|
||||
config.setDataSource(getDataSourceFromConfig(config));
|
||||
config.setDataSource(getDataSourceFromConfig(config, false));
|
||||
}
|
||||
if (config.getReadOnlyDataSource() == null && config.isAutoReadOnlyDataSource()) {
|
||||
config.setReadOnlyDataSource(getDataSourceFromConfig(config, true));
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource getDataSourceFromConfig(ServerConfig config) {
|
||||
private DataSource getDataSourceFromConfig(ServerConfig config, boolean readOnly) {
|
||||
|
||||
if (isOfflineMode(config)) {
|
||||
logger.debug("... DbOffline using platform [{}]", DbOffline.getPlatform());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.getDataSourceJndiName() != null) {
|
||||
if (!readOnly && config.getDataSourceJndiName() != null) {
|
||||
DataSource ds = jndiDataSourceFactory.lookup(config.getDataSourceJndiName());
|
||||
if (ds == null) {
|
||||
throw new PersistenceException("JNDI lookup for DataSource " + config.getDataSourceJndiName() + " returned null.");
|
||||
@@ -292,7 +301,7 @@ public class DefaultContainer implements SpiContainer {
|
||||
}
|
||||
}
|
||||
|
||||
DataSourceConfig dsConfig = config.getDataSourceConfig();
|
||||
DataSourceConfig dsConfig = (readOnly) ? config.getReadOnlyDataSourceConfig() : config.getDataSourceConfig();
|
||||
if (dsConfig == null) {
|
||||
throw new PersistenceException("No DataSourceConfig defined for " + config.getName());
|
||||
}
|
||||
@@ -317,7 +326,14 @@ public class DefaultContainer implements SpiContainer {
|
||||
|
||||
attachListener(config, dsConfig);
|
||||
|
||||
return factory.createPool(config.getName(), dsConfig);
|
||||
if (readOnly) {
|
||||
// setup to use AutoCommit such that we skip explicit commit
|
||||
dsConfig.setAutoCommit(true);
|
||||
//dsConfig.setReadOnly(true);
|
||||
dsConfig.setDefaults(config.getDataSourceConfig());
|
||||
}
|
||||
String poolName = config.getName() + (readOnly ? "-ro" : "");
|
||||
return factory.createPool(poolName, dsConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.meta.MetaBeanInfo;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaObjectGraphNodeStats;
|
||||
import io.ebean.meta.MetaQueryPlanStatistic;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -15,41 +17,32 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
|
||||
private final DefaultServer server;
|
||||
|
||||
public DefaultMetaInfoManager(DefaultServer server) {
|
||||
DefaultMetaInfoManager(DefaultServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetaBeanInfo getMetaBeanInfo(Class<?> beanClass) {
|
||||
return server.getBeanDescriptor(beanClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaBeanInfo> getMetaBeanInfoList() {
|
||||
|
||||
return new ArrayList<>(server.getBeanDescriptors());
|
||||
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
|
||||
return server.collectTransactionStatistics(reset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
|
||||
|
||||
List<MetaQueryPlanStatistic> list = new ArrayList<>();
|
||||
for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) {
|
||||
list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset));
|
||||
CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset);
|
||||
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
|
||||
desc.collectQueryPlanStatistics(collector);
|
||||
}
|
||||
return list;
|
||||
return collector.getList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
|
||||
|
||||
List<MetaObjectGraphNodeStats> list = new ArrayList<>();
|
||||
|
||||
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
|
||||
MetaObjectGraphNodeStats nodeStats = nodeStatistics.get(reset);
|
||||
if (nodeStats.getCount() > 0) {
|
||||
// Only collection non-empty statistics
|
||||
list.add(nodeStats);
|
||||
if (!nodeStatistics.isEmpty()) {
|
||||
list.add(nodeStatistics.get(reset));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.ebean.FutureList;
|
||||
import io.ebean.FutureRowCount;
|
||||
import io.ebean.PagedList;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.SqlQuery;
|
||||
@@ -46,6 +47,7 @@ import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
@@ -65,6 +67,7 @@ import io.ebeaninternal.api.TransactionEventTable;
|
||||
import io.ebeaninternal.dbmigration.DdlGenerator;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneService;
|
||||
import io.ebeaninternal.server.cache.RemoteCacheEvent;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
@@ -102,7 +105,6 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -122,10 +124,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultServer.class);
|
||||
|
||||
private static final int IGNORE_LEADING_ELEMENTS = 5;
|
||||
|
||||
private static final String IO_EBEAN = "io.ebean";
|
||||
|
||||
private final ServerConfig serverConfig;
|
||||
|
||||
private final String serverName;
|
||||
@@ -138,9 +136,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private final DataTimeZone dataTimeZone;
|
||||
|
||||
private final CallStackFactory callStackFactory = new DefaultCallStackFactory();
|
||||
|
||||
private final int maxCallStack;
|
||||
private final CallStackFactory callStackFactory;
|
||||
|
||||
/**
|
||||
* Ebean defaults this to true but for EJB compatible behaviour set this to
|
||||
@@ -253,7 +249,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.updateAllPropertiesInBatch = serverConfig.isUpdateAllPropertiesInBatch();
|
||||
this.collectQueryOrigins = serverConfig.isCollectQueryOrigins();
|
||||
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
|
||||
this.maxCallStack = serverConfig.getMaxCallStack();
|
||||
this.callStackFactory = initCallStackFactory(serverConfig);
|
||||
|
||||
this.rollbackOnChecked = serverConfig.isTransactionRollbackOnChecked();
|
||||
|
||||
@@ -283,6 +279,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
ShutdownManager.registerEbeanServer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the CallStackFactory depending if AutoTune is being used.
|
||||
*/
|
||||
private CallStackFactory initCallStackFactory(ServerConfig serverConfig) {
|
||||
if (!serverConfig.getAutoTuneConfig().isActive()) {
|
||||
// use a common CallStack for performance as we don't care with no AutoTune
|
||||
return new NoopCallStackFactory();
|
||||
}
|
||||
return new DefaultCallStackFactory(serverConfig.getMaxCallStack());
|
||||
}
|
||||
|
||||
private void configureServerPlugins() {
|
||||
|
||||
autoTuneService.startup();
|
||||
@@ -379,6 +386,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return transactionManager.getDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return transactionManager.getReadOnlyDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadAuditPrepare getReadAuditPrepare() {
|
||||
return readAuditPrepare;
|
||||
@@ -402,7 +414,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* Start any services after registering with the ClusterManager.
|
||||
*/
|
||||
public void start() {
|
||||
if (!TenantMode.DB.equals(serverConfig.getTenantMode())) {
|
||||
if (TenantMode.DB != serverConfig.getTenantMode()) {
|
||||
serverConfig.runDbMigration(serverConfig.getDataSource());
|
||||
}
|
||||
}
|
||||
@@ -763,7 +775,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
@Override
|
||||
public void scopedTransactionExit(Object returnOrThrowable, int opCode) {
|
||||
ScopedTransaction st = (ScopedTransaction) transactionScopeManager.getScoped();
|
||||
st.complete(returnOrThrowable, opCode);
|
||||
if (st != null) {
|
||||
// can be null for Supports as that can start as a 'No Transaction' and then
|
||||
// effectively be replaced by transactions inside the scope
|
||||
st.complete(returnOrThrowable, opCode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -820,6 +836,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
break;
|
||||
default:
|
||||
transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel());
|
||||
initNewTransaction(transaction, txScope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,6 +847,22 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return txnContainer;
|
||||
}
|
||||
|
||||
private void initNewTransaction(SpiTransaction transaction, TxScope txScope) {
|
||||
|
||||
if (txScope.isSkipCache()) {
|
||||
transaction.setSkipCache(true);
|
||||
}
|
||||
String label = txScope.getLabel();
|
||||
if (label != null) {
|
||||
transaction.setLabel(label);
|
||||
}
|
||||
ProfileLocation profileLocation = txScope.getProfileLocation();
|
||||
if (profileLocation != null) {
|
||||
profileLocation.obtain();
|
||||
transaction.setProfileLocation(profileLocation);
|
||||
}
|
||||
}
|
||||
|
||||
private TxScope initTxScope(TxScope txScope) {
|
||||
if (txScope == null) {
|
||||
return new TxScope();
|
||||
@@ -1100,6 +1133,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
private <T> SpiOrmQueryRequest<T> createQueryRequest(SpiQuery<T> query, Transaction t) {
|
||||
|
||||
if (t == null) {
|
||||
t = currentServerTransaction();
|
||||
}
|
||||
query.setDefaultRawSqlIfRequired();
|
||||
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
|
||||
// use deployment FetchType.LAZY/EAGER annotations
|
||||
@@ -1109,6 +1145,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
query.selectAllForLazyLoadProperty();
|
||||
|
||||
ProfileLocation profileLocation = query.getProfileLocation();
|
||||
if (profileLocation != null) {
|
||||
profileLocation.obtain();
|
||||
}
|
||||
// if determine cost and no origin for AutoTune
|
||||
if (query.getParentNode() == null) {
|
||||
query.setOrigin(createCallStack());
|
||||
@@ -1150,7 +1190,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!query.isUseBeanCache() || (t != null && t.isSkipCache())) {
|
||||
if (!query.isBeanCacheGet() || (t != null && t.isSkipCache())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1162,7 +1202,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
* Return true if transactions PersistenceContext should be used.
|
||||
*/
|
||||
private <T> boolean useTransactionPersistenceContext(SpiQuery<T> query) {
|
||||
return PersistenceContextScope.TRANSACTION.equals(getPersistenceContextScope(query));
|
||||
return PersistenceContextScope.TRANSACTION == getPersistenceContextScope(query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1179,7 +1219,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
|
||||
spiQuery.setType(Type.BEAN);
|
||||
if (SpiQuery.Mode.NORMAL.equals(spiQuery.getMode()) && !spiQuery.isLoadBeanCache()) {
|
||||
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
|
||||
// See if we can skip doing the fetch completely by getting the bean from the
|
||||
// persistence context or the bean cache
|
||||
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
|
||||
@@ -1189,6 +1229,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(spiQuery, t);
|
||||
request.profileLocationById();
|
||||
if (request.isUseDocStore()) {
|
||||
return docStore().find(request);
|
||||
}
|
||||
@@ -1210,29 +1251,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
public <T> T findOne(Query<T> query, Transaction transaction) {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
|
||||
spiQuery.checkIdEqualTo();
|
||||
Object id = spiQuery.getId();
|
||||
if (id != null) {
|
||||
if (spiQuery.isFindById()) {
|
||||
// actually a find by Id query
|
||||
return findId(query, transaction);
|
||||
}
|
||||
|
||||
SpiTransaction t = (SpiTransaction) transaction;
|
||||
if (t == null) {
|
||||
t = currentServerTransaction();
|
||||
}
|
||||
if (t == null || !t.isSkipCache()) {
|
||||
id = spiQuery.getBeanDescriptor().cacheNaturalKeyIdLookup(spiQuery);
|
||||
if (id != null) {
|
||||
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, id);
|
||||
if (bean != null) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
if (transaction == null) {
|
||||
transaction = currentServerTransaction();
|
||||
}
|
||||
|
||||
// a query that is expected to return either 0 or 1 beans
|
||||
List<T> list = findList(query, t);
|
||||
List<T> list = findList(query, transaction, true);
|
||||
return extractUnique(list);
|
||||
}
|
||||
|
||||
@@ -1515,14 +1544,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> findList(Query<T> query, Transaction t) {
|
||||
return findList(query, t, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
|
||||
request.profileLocationAll();
|
||||
request.resetBeanCacheAutoMode(findOne);
|
||||
Object result = request.getFromQueryCache();
|
||||
if (result != null) {
|
||||
return (List<T>) result;
|
||||
}
|
||||
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
|
||||
return request.getBeanCacheHits();
|
||||
}
|
||||
if (request.isUseDocStore()) {
|
||||
return docStore().findList(request);
|
||||
}
|
||||
@@ -1573,7 +1611,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
public List<SqlRow> findList(SqlQuery query, Transaction t) {
|
||||
|
||||
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
|
||||
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
return request.findList();
|
||||
@@ -2068,6 +2105,30 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
@Override
|
||||
public void remoteTransactionEvent(RemoteTransactionEvent event) {
|
||||
transactionManager.remoteTransactionEvent(event);
|
||||
processRemoteCacheEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a cache event coming from another server in the cluster.
|
||||
*/
|
||||
private void processRemoteCacheEvent(RemoteTransactionEvent event) {
|
||||
RemoteCacheEvent cacheEvent = event.getRemoteCacheEvent();
|
||||
if (cacheEvent != null) {
|
||||
if (cacheEvent.isClearAll()) {
|
||||
serverCacheManager.clearAllLocal();
|
||||
} else {
|
||||
List<String> caches = cacheEvent.getClearCaches();
|
||||
if (caches != null) {
|
||||
for (String cache : caches) {
|
||||
try {
|
||||
serverCacheManager.clearLocal(Class.forName(cache));
|
||||
} catch (Exception e) {
|
||||
logger.error("Error clearing local cache for type " + cache, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <P> P executeInTrans(Function<SpiTransaction, P> fun, Transaction t) {
|
||||
@@ -2120,35 +2181,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
*/
|
||||
@Override
|
||||
public CallStack createCallStack() {
|
||||
|
||||
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
|
||||
|
||||
// ignore the first 6 as they are always avaje stack elements
|
||||
int startIndex = IGNORE_LEADING_ELEMENTS;
|
||||
|
||||
// find the first non-avaje stackElement
|
||||
for (; startIndex < stackTrace.length; startIndex++) {
|
||||
if (!stackTrace[startIndex].getClassName().startsWith(IO_EBEAN)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int stackLength = stackTrace.length - startIndex;
|
||||
if (stackLength > maxCallStack) {
|
||||
// maximum of maxCallStack stackTrace elements
|
||||
stackLength = maxCallStack;
|
||||
}
|
||||
|
||||
// create the 'interesting' part of the stackTrace
|
||||
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
|
||||
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
|
||||
|
||||
if (stackLength < 1) {
|
||||
// this should not really happen
|
||||
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
|
||||
}
|
||||
|
||||
return callStackFactory.createCallStack(finalTrace);
|
||||
return callStackFactory.createCallStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2190,4 +2223,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
|
||||
return transactionManager.collectTransactionStatistics(reset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,8 @@ public class InternalConfiguration {
|
||||
|
||||
private final BootupClasses bootupClasses;
|
||||
|
||||
private final DatabasePlatform databasePlatform;
|
||||
|
||||
private final DeployInherit deployInherit;
|
||||
|
||||
private final TypeManager typeManager;
|
||||
@@ -138,8 +140,8 @@ public class InternalConfiguration {
|
||||
this.serverConfig = serverConfig;
|
||||
this.bootupClasses = bootupClasses;
|
||||
|
||||
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
|
||||
this.expressionFactory = initExpressionFactory(serverConfig, databasePlatform);
|
||||
this.databasePlatform = serverConfig.getDatabasePlatform();
|
||||
this.expressionFactory = initExpressionFactory(serverConfig);
|
||||
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
|
||||
this.multiValueBind = createMultiValueBind(databasePlatform.getPlatform());
|
||||
@@ -161,7 +163,7 @@ public class InternalConfiguration {
|
||||
/**
|
||||
* Create and return the ExpressionFactory based on configuration and database platform.
|
||||
*/
|
||||
private ExpressionFactory initExpressionFactory(ServerConfig serverConfig, DatabasePlatform databasePlatform) {
|
||||
private ExpressionFactory initExpressionFactory(ServerConfig serverConfig) {
|
||||
|
||||
boolean nativeIlike = serverConfig.isExpressionNativeIlike() && databasePlatform.isSupportsNativeIlike();
|
||||
return new DefaultExpressionFactory(serverConfig.isExpressionEqualsWithNullAsNoop(), nativeIlike);
|
||||
@@ -260,16 +262,17 @@ public class InternalConfiguration {
|
||||
* Return the JSON expression handler for the given database platform.
|
||||
*/
|
||||
private DbExpressionHandler getDbExpressionHandler(DatabasePlatform databasePlatform) {
|
||||
final Platform platform = databasePlatform.getPlatform();
|
||||
Platform platform = databasePlatform.getPlatform();
|
||||
String concatOperator = databasePlatform.getConcatOperator();
|
||||
switch (platform) {
|
||||
case POSTGRES:
|
||||
return new PostgresJsonExpression();
|
||||
return new PostgresDbExpression(concatOperator);
|
||||
case ORACLE:
|
||||
return new OracleDbExpression();
|
||||
return new OracleDbExpression(concatOperator);
|
||||
case SQLSERVER:
|
||||
return new SqlServerJsonExpression();
|
||||
return new SqlServerDbExpression(concatOperator);
|
||||
default:
|
||||
return new NotSupportedDbExpression();
|
||||
return new BasicDbExpression(concatOperator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,11 +410,11 @@ public class InternalConfiguration {
|
||||
case DB:
|
||||
return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
|
||||
case SCHEMA:
|
||||
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantSchemaProvider());
|
||||
return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantSchemaProvider());
|
||||
case CATALOG:
|
||||
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantCatalogProvider());
|
||||
return new MultiTenantDbCatalogSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource(), serverConfig.getTenantCatalogProvider());
|
||||
default:
|
||||
return new SimpleDataSourceProvider(serverConfig.getDataSource());
|
||||
return new SimpleDataSourceProvider(serverConfig.getDataSource(), serverConfig.getReadOnlyDataSource());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final TenantCatalogProvider catalogProvider;
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
private final CatalogDataSource catalogDataSource;
|
||||
private final CatalogDataSource readOnly;
|
||||
|
||||
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantCatalogProvider catalogProvider) {
|
||||
this.tenantProvider = tenantProvider;
|
||||
MultiTenantDbCatalogSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantCatalogProvider catalogProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.catalogProvider = catalogProvider;
|
||||
this.catalogDataSource = new CatalogDataSource();
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
this.catalogDataSource = new CatalogDataSource(dataSource, tenantProvider, catalogProvider);
|
||||
if (readOnlyDataSource == null) {
|
||||
this.readOnly = null;
|
||||
} else {
|
||||
this.readOnly = new CatalogDataSource(readOnlyDataSource, tenantProvider, catalogProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,28 +39,48 @@ public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
|
||||
return catalogDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return catalogDataSource.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnly.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB catalog for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantCatalog() {
|
||||
return catalogProvider.catalog(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
private class CatalogDataSource implements DataSource {
|
||||
|
||||
CatalogDataSource() {
|
||||
final DataSource dataSource;
|
||||
final CurrentTenantProvider tenantProvider;
|
||||
final TenantCatalogProvider catalogProvider;
|
||||
|
||||
CatalogDataSource(DataSource dataSource, CurrentTenantProvider tenantProvider, TenantCatalogProvider catalogProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.tenantProvider = tenantProvider;
|
||||
this.catalogProvider = catalogProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB catalog for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantCatalog() {
|
||||
return catalogProvider.catalog(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,19 +17,21 @@ import java.util.logging.Logger;
|
||||
*/
|
||||
class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final TenantSchemaProvider schemaProvider;
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
private final SchemaDataSource schemaDataSource;
|
||||
private final SchemaDataSource readOnly;
|
||||
|
||||
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantSchemaProvider schemaProvider) {
|
||||
this.tenantProvider = tenantProvider;
|
||||
MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, DataSource readOnlyDataSource, TenantSchemaProvider schemaProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.schemaProvider = schemaProvider;
|
||||
this.schemaDataSource = new SchemaDataSource();
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
this.schemaDataSource = new SchemaDataSource(dataSource, schemaProvider, tenantProvider);
|
||||
if (readOnlyDataSource == null) {
|
||||
this.readOnly = null;
|
||||
} else {
|
||||
this.readOnly = new SchemaDataSource(readOnlyDataSource, schemaProvider, tenantProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,28 +39,44 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
return schemaDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return schemaDataSource.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnly.getConnectionForTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool) {
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB schema for the current user Tenant Id.
|
||||
* Tenant schema aware DataSource.
|
||||
*/
|
||||
private String tenantSchema() {
|
||||
return schemaProvider.schema(tenantProvider.currentId());
|
||||
}
|
||||
private static class SchemaDataSource implements DataSource {
|
||||
|
||||
private class SchemaDataSource implements DataSource {
|
||||
private final DataSource dataSource;
|
||||
private final TenantSchemaProvider schemaProvider;
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
SchemaDataSource() {
|
||||
SchemaDataSource(DataSource dataSource, TenantSchemaProvider schemaProvider, CurrentTenantProvider tenantProvider) {
|
||||
this.dataSource = dataSource;
|
||||
this.schemaProvider = schemaProvider;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +88,13 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DB schema for the current user Tenant Id.
|
||||
*/
|
||||
private String tenantSchema() {
|
||||
return schemaProvider.schema(tenantProvider.currentId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connection with the appropriate DB schema set.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,12 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
|
||||
this.dataSourceProvider = dataSourceProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
// read only datasource not supported with DB per tenant at this stage
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return dataSourceProvider.dataSource(tenantProvider.currentId());
|
||||
@@ -32,6 +38,11 @@ class MultiTenantDbSupplier implements DataSourceSupplier {
|
||||
return dataSourceProvider.dataSource(tenantId).getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
throw new SQLException("Not currently supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
dataSourceProvider.shutdown(deregisterDriver);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
@@ -24,6 +25,16 @@ class NoTransaction implements SpiTransaction {
|
||||
|
||||
static final NoTransaction INSTANCE = new NoTransaction();
|
||||
|
||||
@Override
|
||||
public void setLabel(String label) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
// always false
|
||||
@@ -416,4 +427,14 @@ class NoTransaction implements SpiTransaction {
|
||||
public ProfileStream profileStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProfileLocation(ProfileLocation profileLocation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileLocation getProfileLocation() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.bean.CallStack;
|
||||
|
||||
/**
|
||||
* A CallStackFactory we can use when we don't use AutoTune.
|
||||
*/
|
||||
class NoopCallStackFactory implements CallStackFactory {
|
||||
|
||||
private final CallStack COMMON = new CallStack(Thread.currentThread().getStackTrace(), 0, 0);
|
||||
|
||||
@Override
|
||||
public CallStack createCallStack() {
|
||||
return COMMON;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,11 @@ import io.ebeaninternal.server.expression.Op;
|
||||
/**
|
||||
* Oracle JSON expression handler, ARRAY expressions not supported.
|
||||
*/
|
||||
public class OracleDbExpression implements DbExpressionHandler {
|
||||
public class OracleDbExpression extends BaseDbExpression {
|
||||
|
||||
OracleDbExpression(String concatOperator) {
|
||||
super(concatOperator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.common.BeanList;
|
||||
import io.ebean.common.CopyOnFirstWriteList;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.text.json.JsonReadOptions;
|
||||
import io.ebeaninternal.api.BeanCacheResult;
|
||||
import io.ebeaninternal.api.CQueryPlanKey;
|
||||
import io.ebeaninternal.api.HashQuery;
|
||||
import io.ebeaninternal.api.LoadContext;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.NaturalKeySet;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.SpiQuery.Type;
|
||||
@@ -73,6 +78,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
|
||||
private SpiQuerySecondary secondaryQueries;
|
||||
|
||||
private List<T> cacheBeans;
|
||||
|
||||
/**
|
||||
* Create the InternalQueryRequest.
|
||||
*/
|
||||
@@ -89,6 +96,20 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
return queryEngine.translate(this, bindLog, sql, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profileLocationById() {
|
||||
if (query.getProfileLocation() == null) {
|
||||
query.setProfileLocation(beanDescriptor.profileLocationById());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profileLocationAll() {
|
||||
if (query.getProfileLocation() == null && query.isFindAll()) {
|
||||
query.setProfileLocation(beanDescriptor.profileLocationAll());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMultiValueIdSupported() {
|
||||
return beanDescriptor.isMultiValueIdSupported();
|
||||
@@ -234,13 +255,15 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
public void initTransIfRequired() {
|
||||
// first check if the query requires its own transaction
|
||||
if (transaction == null) {
|
||||
// maybe a current one
|
||||
transaction = ebeanServer.currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
if (query.getType().isUpdate()) {
|
||||
// bulk update or delete query
|
||||
transaction = ebeanServer.beginServerTransaction();
|
||||
} else {
|
||||
// create an implicit transaction to execute this query
|
||||
// potentially using read-only DataSource with autoCommit
|
||||
transaction = ebeanServer.createQueryTransaction(query.getTenantId());
|
||||
createdTransaction = true;
|
||||
}
|
||||
createdTransaction = true;
|
||||
}
|
||||
persistenceContext = getPersistenceContext(query, transaction);
|
||||
loadContext = new DLoadContext(this, secondaryQueries);
|
||||
@@ -495,8 +518,79 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
beanDescriptor.putQueryPlan(queryPlanKey, queryPlan);
|
||||
}
|
||||
|
||||
public boolean isUseBeanCache() {
|
||||
return query.isUseBeanCache();
|
||||
@Override
|
||||
public void resetBeanCacheAutoMode(boolean findOne) {
|
||||
query.resetBeanCacheAutoMode(findOne);
|
||||
}
|
||||
|
||||
public boolean isQueryCachePut() {
|
||||
return cacheKey != null && query.getUseQueryCache().isPut();
|
||||
}
|
||||
|
||||
public boolean isBeanCachePut() {
|
||||
return !transaction.isSkipCache() && query.isBeanCachePut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge in prior L2 bean cache hits with the query result.
|
||||
*/
|
||||
public void mergeCacheHits(BeanCollection<T> result) {
|
||||
|
||||
if (cacheBeans != null && !cacheBeans.isEmpty()) {
|
||||
for (T hit : cacheBeans) {
|
||||
result.internalAdd(hit);
|
||||
}
|
||||
// resort in memory here after merging the cache hits with the DB hits
|
||||
if (result instanceof BeanList) {
|
||||
OrderBy<T> orderBy = query.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
beanDescriptor.sort(((BeanList<T>)result).getActualList(), orderBy.toStringFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getBeanCacheHits() {
|
||||
OrderBy<T> orderBy = query.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
|
||||
}
|
||||
return cacheBeans;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFromBeanCache() {
|
||||
|
||||
if (!query.isBeanCacheGet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if the query can use the bean cache
|
||||
// 1. Find by Ids
|
||||
// - hit beanCache with Ids
|
||||
// - keep cache beans, ensure query modified to fetch misses
|
||||
// - query and Load misses into bean cache
|
||||
// - merge the 2 results and return
|
||||
//
|
||||
|
||||
if (!beanDescriptor.isNaturalKeyCaching()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NaturalKeyQueryData<T> data = query.naturalKey();
|
||||
if (data != null) {
|
||||
NaturalKeySet naturalKeySet = data.buildKeys();
|
||||
if (naturalKeySet != null) {
|
||||
// use the natural keys to lookup Ids to then hit the bean cache
|
||||
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, naturalKeySet.keys());
|
||||
// adjust the query (IN clause) based on the cache hits
|
||||
this.cacheBeans = data.removeHits(cacheResult);
|
||||
return data.allHits();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -506,7 +600,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getFromQueryCache() {
|
||||
|
||||
if (query.getUseQueryCache() == CacheMode.OFF) {
|
||||
if (query.getUseQueryCache() == CacheMode.OFF || (transaction != null && transaction.isSkipCache())) {
|
||||
return null;
|
||||
} else {
|
||||
cacheKey = query.queryHash();
|
||||
|
||||
+5
-1
@@ -6,7 +6,11 @@ import io.ebeaninternal.server.expression.Op;
|
||||
/**
|
||||
* Postgres JSON and ARRAY expression handler
|
||||
*/
|
||||
public class PostgresJsonExpression implements DbExpressionHandler {
|
||||
public class PostgresDbExpression extends BaseDbExpression {
|
||||
|
||||
PostgresDbExpression(String concatOperator) {
|
||||
super(concatOperator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
@@ -61,7 +61,7 @@ public final class RelationalQueryRequest {
|
||||
/**
|
||||
* Create the BeanFindRequest.
|
||||
*/
|
||||
public RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
|
||||
RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
|
||||
this.ebeanServer = server;
|
||||
this.queryEngine = engine;
|
||||
this.query = (SpiSqlQuery) q;
|
||||
|
||||
@@ -14,8 +14,11 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
SimpleDataSourceProvider(DataSource dataSource) {
|
||||
private final DataSource readOnlyDataSource;
|
||||
|
||||
SimpleDataSourceProvider(DataSource dataSource, DataSource readOnlyDataSource) {
|
||||
this.dataSource = dataSource;
|
||||
this.readOnlyDataSource = readOnlyDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,13 +26,26 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getReadOnlyDataSource() {
|
||||
return readOnlyDataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(Object tenantId) throws SQLException {
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getReadOnlyConnection(Object tenantId) throws SQLException {
|
||||
return readOnlyDataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown(boolean deregisterDriver) {
|
||||
if (readOnlyDataSource instanceof DataSourcePool){
|
||||
((DataSourcePool) readOnlyDataSource).shutdown(false);
|
||||
}
|
||||
if (dataSource instanceof DataSourcePool){
|
||||
((DataSourcePool) dataSource).shutdown(deregisterDriver);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,24 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
|
||||
*/
|
||||
<A> A getFromQueryCache();
|
||||
|
||||
/**
|
||||
* Maybe hit the bean cache returning true if everything was obtained from the
|
||||
* cache (that there were no misses).
|
||||
*
|
||||
* Do this for findList() on many natural keys or many Ids.
|
||||
*/
|
||||
boolean getFromBeanCache();
|
||||
|
||||
/**
|
||||
* Return the bean cache hits (when all hits / no misses).
|
||||
*/
|
||||
List<T> getBeanCacheHits();
|
||||
|
||||
/**
|
||||
* Reset Bean cache mode AUTO - require explicit setting for bean cache use with findList().
|
||||
*/
|
||||
void resetBeanCacheAutoMode(boolean findOne);
|
||||
|
||||
/**
|
||||
* Return the Database platform like clause.
|
||||
*/
|
||||
@@ -137,4 +155,14 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
|
||||
* Return true if this query is expected to use the doc store.
|
||||
*/
|
||||
boolean isUseDocStore();
|
||||
|
||||
/**
|
||||
* Set profile location for "find by id" if not set.
|
||||
*/
|
||||
void profileLocationById();
|
||||
|
||||
/**
|
||||
* Set profile location for "find all" if not set.
|
||||
*/
|
||||
void profileLocationAll();
|
||||
}
|
||||
|
||||
+5
-1
@@ -6,7 +6,11 @@ import io.ebeaninternal.server.expression.Op;
|
||||
/**
|
||||
* Microsoft SQL Server JSON. ARRAY expressions not supported.
|
||||
*/
|
||||
public class SqlServerJsonExpression implements DbExpressionHandler {
|
||||
public class SqlServerDbExpression extends BaseDbExpression {
|
||||
|
||||
SqlServerDbExpression(String concatOperator) {
|
||||
super(concatOperator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void json(final SpiExpressionRequest request, final String propName,
|
||||
@@ -39,10 +39,10 @@ public class BeanCollectionHelpFactory {
|
||||
|
||||
SpiQuery.Type manyType = request.getQuery().getType();
|
||||
|
||||
if (manyType.equals(SpiQuery.Type.LIST)) {
|
||||
if (manyType == SpiQuery.Type.LIST) {
|
||||
return LIST_HELP;
|
||||
|
||||
} else if (manyType.equals(SpiQuery.Type.SET)) {
|
||||
} else if (manyType == SpiQuery.Type.SET) {
|
||||
return SET_HELP;
|
||||
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
@@ -27,12 +28,12 @@ import io.ebean.event.changelog.ChangeType;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.meta.MetaBeanInfo;
|
||||
import io.ebean.meta.MetaQueryPlanStatistic;
|
||||
import io.ebean.plugin.BeanDocType;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.BeanCacheResult;
|
||||
import io.ebeaninternal.api.CQueryPlanKey;
|
||||
import io.ebeaninternal.api.ConcurrencyMode;
|
||||
import io.ebeaninternal.api.LoadContext;
|
||||
@@ -61,8 +62,7 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.DmlUtil;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
@@ -93,12 +93,13 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Describes Beans including their deployment information.
|
||||
*/
|
||||
public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
public class BeanDescriptor<T> implements BeanType<T> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
|
||||
|
||||
@@ -117,6 +118,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
private final Map<String, String> namedQuery;
|
||||
|
||||
private final short profileBeanId;
|
||||
private final ProfileLocation locationById;
|
||||
private final ProfileLocation locationAll;
|
||||
|
||||
private final boolean multiValueSupported;
|
||||
|
||||
@@ -413,6 +416,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
this.name = InternString.intern(deploy.getName());
|
||||
this.baseTableAlias = "t0";
|
||||
this.fullName = InternString.intern(deploy.getFullName());
|
||||
this.locationById = ProfileLocation.createAt(fullName+".byId");
|
||||
this.locationAll = ProfileLocation.createAt(fullName+".all");
|
||||
this.profileBeanId = deploy.getProfileId();
|
||||
this.beanType = deploy.getBeanType();
|
||||
this.rootBeanType = PersistenceContextUtil.root(beanType);
|
||||
@@ -535,6 +540,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a location for "find by id".
|
||||
*/
|
||||
public ProfileLocation profileLocationById() {
|
||||
return locationById;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a location for "find all".
|
||||
*/
|
||||
public ProfileLocation profileLocationAll() {
|
||||
return locationAll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id used in profiling to identify the bean type.
|
||||
*/
|
||||
@@ -1189,6 +1208,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key properties.
|
||||
*/
|
||||
public String[] getNaturalKey() {
|
||||
return cacheHelp.getNaturalKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching for this type.
|
||||
*/
|
||||
@@ -1204,6 +1230,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return cacheHelp.isBeanCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is a natural key defined for this bean type.
|
||||
*/
|
||||
public boolean isNaturalKeyCaching() {
|
||||
return cacheHelp.isNaturalKeyCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is query caching for this type of bean.
|
||||
*/
|
||||
@@ -1332,6 +1365,17 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
cacheHelp.beanCachePut(bean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cacheBeanPutAll(Collection<?> beans) {
|
||||
if (!beans.isEmpty()) {
|
||||
cacheHelp.beanPutAll((Collection<EntityBean>)beans);
|
||||
}
|
||||
}
|
||||
|
||||
void cacheBeanPutAllDirect(Collection<EntityBean> beans) {
|
||||
cacheHelp.beanCachePutAllDirect(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the cache as the correct type.
|
||||
*/
|
||||
@@ -1370,10 +1414,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to hit the cache using the natural key.
|
||||
* Use natural key lookup to hit the bean cache.
|
||||
*/
|
||||
public Object cacheNaturalKeyIdLookup(SpiQuery<T> query) {
|
||||
return cacheHelp.naturalKeyIdLookup(query);
|
||||
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
|
||||
return cacheHelp.naturalKeyLookup(context, keys);
|
||||
}
|
||||
|
||||
public void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
@@ -1501,25 +1545,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return new DeployUpdateParser(this).parse(ormUpdateStatement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
|
||||
return collectQueryPlanStatisticsInternal(reset, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset) {
|
||||
return collectQueryPlanStatisticsInternal(reset, false);
|
||||
}
|
||||
|
||||
public List<MetaQueryPlanStatistic> collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) {
|
||||
List<MetaQueryPlanStatistic> list = new ArrayList<>(queryPlanCache.size());
|
||||
public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
Snapshot snapshot = queryPlan.getSnapshot(reset);
|
||||
if (collectAll || snapshot.getExecutionCount() > 0) {
|
||||
list.add(snapshot);
|
||||
if (!queryPlan.isEmptyStats()) {
|
||||
collector.add(queryPlan.getSnapshot(collector.isReset()));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2325,7 +2356,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
* Return the property path given the db table and column.
|
||||
*/
|
||||
public String findBeanPath(String tableName, String columnName) {
|
||||
if (tableName.length() == 0 || tableName.equalsIgnoreCase(baseTable)) {
|
||||
if (tableName.isEmpty() || tableName.equalsIgnoreCase(baseTable)) {
|
||||
return columnPath.get(columnName);
|
||||
}
|
||||
BeanPropertyAssoc<?> assocProperty = tablePath.get(tableName);
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.BeanCacheResult;
|
||||
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
@@ -16,16 +16,18 @@ import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages the bean, query and collection caches.
|
||||
@@ -57,7 +59,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
private final String cacheName;
|
||||
|
||||
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
|
||||
private final String naturalKeyProperty;
|
||||
private final String[] naturalKey;
|
||||
|
||||
private final ServerCache beanCache;
|
||||
private final ServerCache naturalKeyCache;
|
||||
@@ -83,7 +85,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKeyProperty = cacheOptions.getNaturalKey();
|
||||
this.naturalKey = cacheOptions.getNaturalKey();
|
||||
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
@@ -155,6 +157,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is natural key caching for this type of bean.
|
||||
*/
|
||||
boolean isNaturalKeyCaching() {
|
||||
return naturalKeyCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching on this type.
|
||||
*/
|
||||
@@ -162,6 +171,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return beanCache != null || queryCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key properties.
|
||||
*/
|
||||
String[] getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
|
||||
CacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
@@ -314,36 +330,49 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bean using the natural key lookup if available.
|
||||
* Use natural keys to hit the bean cache and return resulting hits.
|
||||
*/
|
||||
Object naturalKeyIdLookup(SpiQuery<T> query) {
|
||||
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
|
||||
|
||||
if (!isNaturalKeyCaching(query.isUseBeanCache())) {
|
||||
// no natural key caching for this query
|
||||
return null;
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
// check if it is a find by unique id (using the natural key)
|
||||
NaturalKeyBindParam keyBindParam = query.getNaturalKeyBindParam();
|
||||
if (keyBindParam == null || !isNaturalKey(keyBindParam.getName())) {
|
||||
// query is not appropriate
|
||||
return null;
|
||||
}
|
||||
// naturalKey -> Id map
|
||||
Map<Object, Object> naturalKeyMap = naturalKeyCache.getAll(keys);
|
||||
|
||||
// try to lookup the id using the natural key
|
||||
Object id = naturalKeyCache.get(keyBindParam.getValue());
|
||||
if (natLog.isTraceEnabled()) {
|
||||
natLog.trace(" LOOKUP {}({}) - id:{}", cacheName, keyBindParam.getValue(), id);
|
||||
natLog.trace(" LOOKUP Many {}({}) - hits:{}", cacheName, keys, naturalKeyMap);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean isNaturalKeyCaching(Boolean queryUseCache) {
|
||||
return naturalKeyCache != null && (queryUseCache == null || queryUseCache);
|
||||
}
|
||||
BeanCacheResult<T> result = new BeanCacheResult<>();
|
||||
if (naturalKeyMap.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isNaturalKey(String propName) {
|
||||
return propName != null && propName.equals(cacheOptions.getNaturalKey());
|
||||
// create reverse id -> natural key map
|
||||
Map<Object, Object> reverseMap = new HashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : naturalKeyMap.entrySet()) {
|
||||
reverseMap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
|
||||
Set<Object> ids = new HashSet<>(naturalKeyMap.values());
|
||||
Map<Object, Object> beanDataMap = beanCache.getAll(ids);
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET MANY {}({}) - hits:{}", cacheName, ids, beanDataMap.keySet());
|
||||
}
|
||||
// process the hits into beans etc
|
||||
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
|
||||
|
||||
Object id = entry.getKey();
|
||||
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
|
||||
|
||||
T bean = convertToBean(id, false, context, cachedBeanData);
|
||||
Object naturalKey = reverseMap.get(id);
|
||||
result.add(bean, naturalKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,6 +418,22 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return CachedBeanDataFromBean.extract(targetDesc, bean);
|
||||
}
|
||||
|
||||
void beanPutAll(Collection<EntityBean> beans) {
|
||||
if (desc.inheritInfo != null) {
|
||||
Class<?> aClass = theClassOf(beans);
|
||||
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
|
||||
} else {
|
||||
beanCachePutAllDirect(beans);
|
||||
}
|
||||
}
|
||||
|
||||
private Class<?> theClassOf(Collection<EntityBean> beans) {
|
||||
if (beans instanceof List) {
|
||||
return ((List)beans).get(0).getClass();
|
||||
}
|
||||
return beans.iterator().next().getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
@@ -401,9 +446,41 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
void beanCachePutAllDirect(Collection<EntityBean> beans) {
|
||||
|
||||
Map<Object,Object> natKeys = null;
|
||||
if (naturalKey != null) {
|
||||
natKeys = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
Map<Object,Object> map = new LinkedHashMap<>();
|
||||
for (EntityBean bean : beans) {
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
Object id = desc.getId(bean);
|
||||
map.put(id, beanData);
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
natKeys.put(naturalKey, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT ALL {}({})", cacheName, map.keySet());
|
||||
}
|
||||
getBeanCache().putAll(map);
|
||||
|
||||
if (natKeys != null && !natKeys.isEmpty()) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT ALL {}({}, {})", cacheName, naturalKey, natKeys.keySet());
|
||||
}
|
||||
naturalKeyCache.putAll(natKeys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
@@ -414,8 +491,8 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
|
||||
if (naturalKeyProperty != null) {
|
||||
Object naturalKey = beanData.getData(naturalKeyProperty);
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
@@ -425,6 +502,21 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private Object calculateNaturalKey(CachedBeanData beanData) {
|
||||
if (naturalKey.length == 1) {
|
||||
return beanData.getData(naturalKey[0]);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
Object val = beanData.getData(key);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
sb.append(val).append(";");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
}
|
||||
@@ -450,6 +542,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
return convertToBean(id, readOnly, context, data);
|
||||
}
|
||||
|
||||
private T convertToBean(Object id, Boolean readOnly, PersistenceContext context, CachedBeanData data) {
|
||||
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
|
||||
Object bean = data.getSharableBean();
|
||||
if (bean != null) {
|
||||
@@ -510,9 +609,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
ebi.setPersistenceContext(context);
|
||||
desc.contextPut(context, id, bean);
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
}
|
||||
if (desc.isReadAuditing()) {
|
||||
desc.readAuditBean("l2", "", bean);
|
||||
}
|
||||
@@ -716,7 +812,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = existingData.getData(naturalKeyProperty);
|
||||
Object oldKey = calculateNaturalKey(existingData);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
|
||||
@@ -1287,12 +1287,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdType.SEQUENCE.equals(desc.getIdType()) && !dbIdentity.isSupportsSequence()) {
|
||||
if (IdType.SEQUENCE == desc.getIdType() && !dbIdentity.isSupportsSequence()) {
|
||||
// explicit sequence but not supported by the DatabasePlatform
|
||||
logger.info("Explicit sequence on " + desc.getFullName() + " but not supported by DB Platform - ignored");
|
||||
desc.setIdType(null);
|
||||
}
|
||||
if (IdType.IDENTITY.equals(desc.getIdType()) && !dbIdentity.isSupportsIdentity()) {
|
||||
if (IdType.IDENTITY == desc.getIdType() && !dbIdentity.isSupportsIdentity()) {
|
||||
// explicit identity but not supported by the DatabasePlatform
|
||||
logger.info("Explicit Identity on " + desc.getFullName() + " but not supported by DB Platform - ignored");
|
||||
desc.setIdType(null);
|
||||
@@ -1316,7 +1316,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
return;
|
||||
}
|
||||
|
||||
if (IdType.IDENTITY.equals(desc.getIdType())) {
|
||||
if (IdType.IDENTITY == desc.getIdType()) {
|
||||
// used when getGeneratedKeys is not supported (SQL Server 2000)
|
||||
String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable());
|
||||
desc.setSelectLastInsertedId(selectLastInsertedId);
|
||||
@@ -1392,7 +1392,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
Integer pos = reflectProps.getPropertyIndex(propName);
|
||||
if (pos == null) {
|
||||
if (isPersistentField(prop)) {
|
||||
throw new IllegalStateException("Property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
|
||||
throw new IllegalStateException(
|
||||
"If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " +
|
||||
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -327,16 +327,16 @@ public class DeployBeanDescriptor<T> {
|
||||
}
|
||||
|
||||
public boolean isEmbedded() {
|
||||
return EntityType.EMBEDDED.equals(entityType);
|
||||
return EntityType.EMBEDDED == entityType;
|
||||
}
|
||||
|
||||
public boolean isBaseTableType() {
|
||||
EntityType et = getEntityType();
|
||||
return EntityType.ORM.equals(et);
|
||||
return EntityType.ORM == et;
|
||||
}
|
||||
|
||||
public boolean isDocStoreOnly() {
|
||||
return EntityType.DOC.equals(entityType);
|
||||
return EntityType.DOC == entityType;
|
||||
}
|
||||
|
||||
public EntityType getEntityType() {
|
||||
@@ -405,17 +405,17 @@ public class DeployBeanDescriptor<T> {
|
||||
*/
|
||||
public void setCache(Cache cache) {
|
||||
|
||||
String naturalKey = null;
|
||||
if (!cache.naturalKey().isEmpty()) {
|
||||
// find the property and mark as natural key property
|
||||
String propName = cache.naturalKey().trim();
|
||||
DeployBeanProperty beanProperty = getBeanProperty(propName);
|
||||
String[] properties = cache.naturalKey();
|
||||
for (String property : properties) {
|
||||
DeployBeanProperty beanProperty = getBeanProperty(property);
|
||||
if (beanProperty != null) {
|
||||
beanProperty.setNaturalKey();
|
||||
naturalKey = propName;
|
||||
}
|
||||
}
|
||||
this.cacheOptions = new CacheOptions(cache, naturalKey);
|
||||
if (properties.length == 0) {
|
||||
properties = null;
|
||||
}
|
||||
this.cacheOptions = new CacheOptions(cache, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -581,7 +581,7 @@ public class DeployBeanProperty {
|
||||
* Set the default fetch type for this property.
|
||||
*/
|
||||
public void setFetchType(FetchType fetchType) {
|
||||
this.fetchEager = FetchType.EAGER.equals(fetchType);
|
||||
this.fetchEager = FetchType.EAGER == fetchType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user