mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ec5fe6a85 | ||
|
|
9ea5505214 | ||
|
|
75d3b566a7 | ||
|
|
e38059f52c | ||
|
|
72331aa945 | ||
|
|
e497c54f82 | ||
|
|
b44091ff3a | ||
|
|
2ab889f889 | ||
|
|
23541e661e | ||
|
|
6778e37652 | ||
|
|
5eac31283d | ||
|
|
38016773d4 | ||
|
|
ad80eb267f | ||
|
|
449e0832e0 | ||
|
|
f28f5c9a01 | ||
|
|
9dd28d8879 | ||
|
|
b47eec99a5 | ||
|
|
99bb55efa0 | ||
|
|
4408eb618a | ||
|
|
9ccd1b1b27 | ||
|
|
0c1657fef5 | ||
|
|
ee393c6485 | ||
|
|
7f68b9d846 | ||
|
|
2035a3344e | ||
|
|
ade36582a0 | ||
|
|
a4b8be61e3 | ||
|
|
b7feb62ecd | ||
|
|
7e48f7c8eb | ||
|
|
501f8111fe | ||
|
|
d8f4503d98 | ||
|
|
0d83978c58 | ||
|
|
69e56be459 | ||
|
|
35a721039d | ||
|
|
98b4ac5029 | ||
|
|
fe874c946d | ||
|
|
53386e9bf4 | ||
|
|
5435055f5c | ||
|
|
583e10f5c6 | ||
|
|
5393d709a3 | ||
|
|
fc1c9392aa | ||
|
|
6b2c48d4f6 | ||
|
|
400ae3cf4b | ||
|
|
f22a322104 | ||
|
|
e98ff952bf | ||
|
|
573e1b553c | ||
|
|
a674d0d696 | ||
|
|
5393161c16 | ||
|
|
1bdc6d8f7f | ||
|
|
e9081a176f | ||
|
|
3e7db2887f | ||
|
|
fcf14ac222 | ||
|
|
55324653a5 | ||
|
|
a132f09b03 | ||
|
|
48eac84930 | ||
|
|
948619a125 |
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -32,7 +32,7 @@
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-config</artifactId>
|
||||
<version>1.2</version>
|
||||
<version>1.3</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -519,11 +519,11 @@ public interface Transaction extends AutoCloseable {
|
||||
void flush() throws PersistenceException;
|
||||
|
||||
/**
|
||||
* This is a synonym for flush() and will be deprecated.
|
||||
* Deprecated - migrate to flush().
|
||||
* <p>
|
||||
* flush() is preferred as it matches the JPA flush() method.
|
||||
* </p>
|
||||
*/
|
||||
@Deprecated
|
||||
void flushBatch() throws PersistenceException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.6.2</tag>
|
||||
<tag>ebean-parent-12.6.6</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean autotune</name>
|
||||
@@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
+32
-14
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -18,8 +18,8 @@
|
||||
<ebean-migration.version>12.4.0</ebean-migration.version>
|
||||
<ebean-test-docker.version>4.1</ebean-test-docker.version>
|
||||
<ebean-datasource.version>7.0</ebean-datasource.version>
|
||||
<ebean-agent.version>12.6.2</ebean-agent.version>
|
||||
<ebean-maven-plugin.version>12.6.2</ebean-maven-plugin.version>
|
||||
<ebean-agent.version>12.6.6</ebean-agent.version>
|
||||
<ebean-maven-plugin.version>12.6.6</ebean-maven-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -81,72 +81,90 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-xml</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-autotune</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<name>ebean core type</name>
|
||||
<description>ebean scalar types api</description>
|
||||
|
||||
<properties>
|
||||
<jackson-core.version>2.11.3</jackson-core.version>
|
||||
@@ -19,7 +21,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.6.2</tag>
|
||||
<tag>ebean-parent-12.6.6</tag>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
@@ -70,7 +70,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>12.2.0</version>
|
||||
<version>12.4.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -92,19 +92,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -301,7 +301,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>12.5.0</version>
|
||||
<version>12.6.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -318,4 +318,20 @@ public interface SpiTransaction extends Transaction {
|
||||
* Return true if explicitly set to skip cache (ignores skipOnWrite).
|
||||
*/
|
||||
boolean isSkipCacheExplicit();
|
||||
|
||||
/**
|
||||
* Fire pre commit processing/listeners.
|
||||
*/
|
||||
void preCommit();
|
||||
|
||||
/**
|
||||
* Fire post commit events and listeners.
|
||||
*/
|
||||
void postCommit();
|
||||
|
||||
/**
|
||||
* Fire post rollback events and listeners.
|
||||
*/
|
||||
void postRollback(Throwable cause);
|
||||
|
||||
}
|
||||
|
||||
@@ -418,4 +418,18 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
transaction.flushBatchOnCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCommit() {
|
||||
transaction.preCommit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommit() {
|
||||
transaction.postCommit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRollback(Throwable cause) {
|
||||
transaction.postRollback(cause);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-22
@@ -27,7 +27,7 @@ public abstract class AbstractSqlQueryRequest {
|
||||
|
||||
protected final SpiEbeanServer server;
|
||||
|
||||
protected SpiTransaction trans;
|
||||
protected SpiTransaction transaction;
|
||||
|
||||
private boolean createdTransaction;
|
||||
|
||||
@@ -47,18 +47,18 @@ public abstract class AbstractSqlQueryRequest {
|
||||
AbstractSqlQueryRequest(SpiEbeanServer server, SpiSqlBinding query, Transaction t) {
|
||||
this.server = server;
|
||||
this.query = query;
|
||||
this.trans = (SpiTransaction) t;
|
||||
this.transaction = (SpiTransaction) t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a transaction if none currently exists.
|
||||
*/
|
||||
public void initTransIfRequired() {
|
||||
if (trans == null) {
|
||||
trans = server.currentServerTransaction();
|
||||
if (trans == null || !trans.isActive()) {
|
||||
if (transaction == null) {
|
||||
transaction = server.currentServerTransaction();
|
||||
if (transaction == null || !transaction.isActive()) {
|
||||
// create a local readOnly transaction
|
||||
trans = server.createReadOnlyTransaction(null);
|
||||
transaction = server.createReadOnlyTransaction(null);
|
||||
createdTransaction = true;
|
||||
}
|
||||
}
|
||||
@@ -69,20 +69,18 @@ public abstract class AbstractSqlQueryRequest {
|
||||
*/
|
||||
public void endTransIfRequired() {
|
||||
if (createdTransaction) {
|
||||
trans.commit();
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
public EbeanServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public SpiTransaction getTransaction() {
|
||||
return trans;
|
||||
protected void flushJdbcBatchOnQuery() {
|
||||
if (transaction.isFlushOnQuery()) {
|
||||
transaction.flush();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLogSql() {
|
||||
return trans.isLogSql();
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +118,6 @@ public abstract class AbstractSqlQueryRequest {
|
||||
* Prepare the SQL taking into account named bind parameters.
|
||||
*/
|
||||
private void prepareSql() {
|
||||
|
||||
String sql = query.getQuery();
|
||||
BindParams bindParams = query.getBindParams();
|
||||
if (!bindParams.isEmpty()) {
|
||||
@@ -131,7 +128,6 @@ public abstract class AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
private String limitOffset(String sql) {
|
||||
|
||||
int firstRow = query.getFirstRow();
|
||||
int maxRows = query.getMaxRows();
|
||||
if (firstRow > 0 || maxRows > 0) {
|
||||
@@ -149,10 +145,8 @@ public abstract class AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
protected void executeAsSql(Binder binder) throws SQLException {
|
||||
|
||||
prepareSql();
|
||||
Connection conn = trans.getInternalConnection();
|
||||
|
||||
Connection conn = transaction.getInternalConnection();
|
||||
pstmt = conn.prepareStatement(sql);
|
||||
if (query.getTimeout() > 0) {
|
||||
pstmt.setQueryTimeout(query.getTimeout());
|
||||
@@ -160,14 +154,12 @@ public abstract class AbstractSqlQueryRequest {
|
||||
if (query.getBufferFetchSizeHint() > 0) {
|
||||
pstmt.setFetchSize(query.getBufferFetchSizeHint());
|
||||
}
|
||||
|
||||
BindParams bindParams = query.getBindParams();
|
||||
if (!bindParams.isEmpty()) {
|
||||
this.bindLog = binder.bind(bindParams, pstmt, conn);
|
||||
}
|
||||
|
||||
if (isLogSql()) {
|
||||
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
|
||||
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
|
||||
}
|
||||
|
||||
setResultSet(pstmt.executeQuery(), null);
|
||||
|
||||
@@ -215,7 +215,6 @@ class DefaultBeanLoader {
|
||||
desc.contextPut(pc, id, bean);
|
||||
ebi.setPersistenceContext(pc);
|
||||
}
|
||||
|
||||
boolean draft = desc.isDraftInstance(bean);
|
||||
|
||||
if (embeddedOwnerIndex == -1) {
|
||||
@@ -245,7 +244,6 @@ class DefaultBeanLoader {
|
||||
// and put the data into the original bean
|
||||
query.setUsageProfiling(false);
|
||||
query.setPersistenceContext(pc);
|
||||
|
||||
query.setMode(mode);
|
||||
query.setId(id);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
|
||||
ormQuery.setManualId();
|
||||
|
||||
// execute the underlying ORM query returning the ResultSet
|
||||
SpiResultSet result = server.findResultSet(ormQuery, trans);
|
||||
SpiResultSet result = server.findResultSet(ormQuery, transaction);
|
||||
this.pstmt = result.getStatement();
|
||||
this.sql = ormQuery.getGeneratedSql();
|
||||
setResultSet(result.getResultSet(), ormQuery.getQueryPlanKey());
|
||||
@@ -88,14 +88,17 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
public void findEach(Consumer<T> consumer) {
|
||||
flushJdbcBatchOnQuery();
|
||||
queryEngine.findEach(this, consumer);
|
||||
}
|
||||
|
||||
public void findEachWhile(Predicate<T> consumer) {
|
||||
flushJdbcBatchOnQuery();
|
||||
queryEngine.findEachWhile(this, consumer);
|
||||
}
|
||||
|
||||
public List<T> findList() {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findList(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
@Override
|
||||
public void preGetterTrigger(int propertyIndex) {
|
||||
if (flushBatchOnGetter(propertyIndex)) {
|
||||
transaction.flushBatch();
|
||||
transaction.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,13 +73,13 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
/**
|
||||
* Add this request to BatchControl to flush later.
|
||||
*/
|
||||
public void addToFlushQueue(boolean early) {
|
||||
public void addToFlushQueue(int pos) {
|
||||
BatchControl control = transaction.getBatchControl();
|
||||
if (control == null) {
|
||||
control = persistExecute.createBatchControl(transaction);
|
||||
}
|
||||
flushQueue = true;
|
||||
control.addToFlushQueue(this, early);
|
||||
control.addToFlushQueue(this, pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -104,18 +104,14 @@ public interface Persister {
|
||||
void executeOrQueue(SpiSqlUpdate update, SpiTransaction t, boolean queue);
|
||||
|
||||
/**
|
||||
* Queue the SqlUpdate for early execution (with JDBC batch).
|
||||
* Queue the SqlUpdate for execution with position 0, 1 or 2 defining
|
||||
* when it executes relative to the flush of beans .
|
||||
*/
|
||||
void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t);
|
||||
void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t, int pos);
|
||||
|
||||
/**
|
||||
* Queue the SqlUpdate for late execution (with JDBC batch).
|
||||
* Add the statement to JDBC batch for later execution via executeBatch.
|
||||
*/
|
||||
void addToFlushQueueLast(SpiSqlUpdate update, SpiTransaction t);
|
||||
|
||||
/**
|
||||
* Add the statement to JDBC batch for later execution via executeBatch.
|
||||
*/
|
||||
void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,35 +56,43 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
boolean findEachRow(RowConsumer mapper) {
|
||||
flushJdbcBatchOnQuery();
|
||||
queryEngine.findEachRow(this, mapper);
|
||||
return true;
|
||||
}
|
||||
|
||||
<T> List<T> findListMapper(RowMapper<T> mapper) {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findListMapper(this, mapper);
|
||||
}
|
||||
|
||||
<T> T findOneMapper(RowMapper<T> mapper) {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findOneMapper(this, mapper);
|
||||
}
|
||||
|
||||
public <T> List<T> findSingleAttributeList(Class<T> cls) {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findSingleAttributeList(this, cls);
|
||||
}
|
||||
|
||||
public <T> T findSingleAttribute(Class<T> cls) {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findSingleAttribute(this, cls);
|
||||
}
|
||||
|
||||
public void findEach(Consumer<SqlRow> consumer) {
|
||||
flushJdbcBatchOnQuery();
|
||||
queryEngine.findEach(this, consumer);
|
||||
}
|
||||
|
||||
public void findEachWhile(Predicate<SqlRow> consumer) {
|
||||
flushJdbcBatchOnQuery();
|
||||
queryEngine.findEach(this, consumer);
|
||||
}
|
||||
|
||||
public List<SqlRow> findList() {
|
||||
flushJdbcBatchOnQuery();
|
||||
return queryEngine.findList(this);
|
||||
}
|
||||
|
||||
@@ -92,9 +100,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
* Build the list of property names.
|
||||
*/
|
||||
private String[] getPropertyNames() throws SQLException {
|
||||
|
||||
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||
|
||||
int columnsPlusOne = metaData.getColumnCount() + 1;
|
||||
ArrayList<String> propNames = new ArrayList<>(columnsPlusOne - 1);
|
||||
for (int i = 1; i < columnsPlusOne; i++) {
|
||||
@@ -107,9 +113,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
* Read and return the next SqlRow.
|
||||
*/
|
||||
public SqlRow createNewRow() throws SQLException {
|
||||
|
||||
rows++;
|
||||
|
||||
SqlRow sqlRow = queryEngine.createSqlRow(estimateCapacity);
|
||||
int index = 0;
|
||||
for (String propertyName : propertyNames) {
|
||||
@@ -121,9 +125,9 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
public void logSummary() {
|
||||
if (trans.isLogSummary()) {
|
||||
if (transaction.isLogSummary()) {
|
||||
long micros = (System.nanoTime() - startNano) / 1000L;
|
||||
trans.logSummary("SqlQuery rows[" + rows + "] micros[" + micros + "] bind[" + bindLog + "]");
|
||||
transaction.logSummary("SqlQuery rows[" + rows + "] micros[" + micros + "] bind[" + bindLog + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +140,6 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
|
||||
|
||||
List<T> list = new ArrayList<>();
|
||||
while (next()) {
|
||||
list.add(mapper.map(resultSet, rows++));
|
||||
|
||||
@@ -1872,6 +1872,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return query.setId(id).findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference with a check for the bean in the persistence context.
|
||||
*/
|
||||
public EntityBean createReference(Boolean readOnly, Object id, PersistenceContext pc) {
|
||||
Object refBean = contextGet(pc, id);
|
||||
if (refBean == null) {
|
||||
refBean = createReference(readOnly, false, id, pc);
|
||||
}
|
||||
return (EntityBean)refBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reference bean based on the id.
|
||||
*/
|
||||
|
||||
+1
-42
@@ -89,7 +89,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
BeanDescriptorCacheHelp(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
|
||||
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
|
||||
|
||||
this.desc = desc;
|
||||
this.beanType = desc.rootBeanType;
|
||||
this.cacheName = beanType.getSimpleName();
|
||||
@@ -99,7 +98,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKey = cacheOptions.getNaturalKey();
|
||||
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
} else {
|
||||
@@ -133,7 +131,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
void deriveNotifyFlags() {
|
||||
cacheNotifyOnAll = (invalidateQueryCache || beanCache != null || queryCache != null);
|
||||
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
if (cacheNotifyOnAll || cacheNotifyOnDelete) {
|
||||
String notifyMode = cacheNotifyOnAll ? "All" : "Delete";
|
||||
@@ -292,7 +289,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Try to load the bean collection from cache return true if successful.
|
||||
*/
|
||||
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
|
||||
|
||||
if (many.isElementCollection()) {
|
||||
// held as part of the bean cache so skip
|
||||
return false;
|
||||
@@ -307,14 +303,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
EntityBean ownerBean = bc.getOwnerBean();
|
||||
EntityBeanIntercept ebi = ownerBean._ebean_getIntercept();
|
||||
PersistenceContext persistenceContext = ebi.getPersistenceContext();
|
||||
|
||||
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
|
||||
|
||||
List<Object> idList = entry.getIdList();
|
||||
bc.checkEmptyLazyLoad();
|
||||
for (Object id : idList) {
|
||||
Object refBean = targetDescriptor.createReference(readOnly, false, id, persistenceContext);
|
||||
many.add(bc, (EntityBean) refBean);
|
||||
many.add(bc, targetDescriptor.createReference(readOnly, id, persistenceContext));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -323,7 +317,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Put the beanCollection into the cache.
|
||||
*/
|
||||
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
|
||||
|
||||
if (many.isElementCollection()) {
|
||||
CachedBeanData data = (CachedBeanData) beanCache.get(parentId);
|
||||
if (data != null) {
|
||||
@@ -351,7 +344,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) {
|
||||
|
||||
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName);
|
||||
if (manyLog.isDebugEnabled()) {
|
||||
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry);
|
||||
@@ -360,7 +352,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
|
||||
|
||||
Collection<?> actualDetails = BeanCollectionUtil.getActualDetails(details);
|
||||
if (actualDetails == null) {
|
||||
return null;
|
||||
@@ -378,7 +369,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Hit the bean cache with the given ids returning the hits.
|
||||
*/
|
||||
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
|
||||
|
||||
Set<Object> keys = new HashSet<>(ids.size());
|
||||
for (Object id : ids) {
|
||||
keys.add(desc.cacheKey(id));
|
||||
@@ -395,7 +385,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
T bean = convertToBean(entry.getKey(), false, context, cachedBeanData);
|
||||
result.add(bean, desc.getBeanId(bean));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -403,14 +392,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Use natural keys to hit the bean cache and return resulting hits.
|
||||
*/
|
||||
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
|
||||
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
// naturalKey -> Id map
|
||||
Map<Object, Object> naturalKeyMap = naturalKeyCache.getAll(keys);
|
||||
|
||||
if (natLog.isTraceEnabled()) {
|
||||
natLog.trace(" MLOOKUP {}({}) - hits:{}", cacheName, keys, naturalKeyMap);
|
||||
}
|
||||
@@ -433,15 +420,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -452,7 +436,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
// Not using a loadContext for beans coming out of L2 cache
|
||||
// so that means no batch lazy loading for these beans
|
||||
EntityBean entityBean = (EntityBean) bean;
|
||||
@@ -522,7 +505,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Put a bean into the bean cache.
|
||||
*/
|
||||
void beanCachePut(EntityBean bean) {
|
||||
|
||||
if (desc.inheritInfo != null) {
|
||||
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
|
||||
} else {
|
||||
@@ -531,7 +513,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
void beanCachePutAllDirect(Collection<EntityBean> beans) {
|
||||
|
||||
Map<Object, Object> natKeys = null;
|
||||
if (naturalKey != null) {
|
||||
natKeys = new LinkedHashMap<>();
|
||||
@@ -566,15 +547,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
|
||||
String key = desc.cacheKeyForBean(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, key, beanData);
|
||||
}
|
||||
getBeanCache().put(key, beanData);
|
||||
|
||||
if (naturalKey != null) {
|
||||
String naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
@@ -618,7 +596,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Return a bean from the bean cache.
|
||||
*/
|
||||
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
|
||||
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
|
||||
if (data == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
@@ -646,7 +623,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return (T) bean;
|
||||
}
|
||||
}
|
||||
|
||||
return (T) loadBean(id, readOnly, data, context);
|
||||
}
|
||||
|
||||
@@ -654,7 +630,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Load the entity bean taking into account inheritance.
|
||||
*/
|
||||
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return loadBeanDirect(id, readOnly, data, context);
|
||||
@@ -674,7 +649,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Load the entity bean from cache data given this is the root bean type.
|
||||
*/
|
||||
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
id = desc.convertId(id);
|
||||
EntityBean bean = null;
|
||||
if (context == null) {
|
||||
@@ -682,7 +656,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
} else {
|
||||
bean = (EntityBean) desc.contextGet(context, id);
|
||||
}
|
||||
|
||||
if (bean == null) {
|
||||
bean = desc.createEntityBean();
|
||||
desc.setId(id, bean);
|
||||
@@ -698,7 +671,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, data, context);
|
||||
|
||||
if (desc.isReadAuditing()) {
|
||||
desc.readAuditBean("l2", "", bean);
|
||||
}
|
||||
@@ -709,7 +681,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Load the embedded bean checking for inheritance.
|
||||
*/
|
||||
EntityBean embeddedBeanLoad(CachedBeanData data, PersistenceContext context) {
|
||||
|
||||
String discValue = data.getDiscValue();
|
||||
if (discValue == null) {
|
||||
return embeddedBeanLoadDirect(data, context);
|
||||
@@ -746,25 +717,20 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Load a batch of entities from L2 bean cache checking the lazy loaded property is loaded.
|
||||
*/
|
||||
Set<EntityBeanIntercept> beanCacheLoadAll(List<EntityBeanIntercept> list, PersistenceContext context, int lazyLoadProperty, String propertyName) {
|
||||
|
||||
Map<Object, EntityBeanIntercept> ebis = new HashMap<>();
|
||||
for (EntityBeanIntercept ebi : list) {
|
||||
ebis.put(desc.cacheKeyForBean(ebi.getOwner()), ebi);
|
||||
}
|
||||
|
||||
|
||||
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" MLOAD {}({}) - got hits ({})", cacheName, ebis.keySet(), hits.size());
|
||||
}
|
||||
|
||||
Set<EntityBeanIntercept> loaded = new HashSet<>();
|
||||
|
||||
Iterator<Map.Entry<Object, Object>> iterator = hits.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Object, Object> hit = iterator.next();
|
||||
|
||||
Object key = hit.getKey();
|
||||
EntityBeanIntercept ebi = ebis.remove(key);
|
||||
CachedBeanData cacheData = (CachedBeanData) hit.getValue();
|
||||
@@ -774,7 +740,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
beanLog.trace(" load {}({}) - cache miss on property({})", cacheName, key, propertyName);
|
||||
}
|
||||
iterator.remove();
|
||||
|
||||
} else {
|
||||
CachedBeanDataToBean.load(desc, ebi.getOwner(), cacheData, context);
|
||||
loaded.add(ebi);
|
||||
@@ -783,11 +748,9 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ebis.isEmpty() && beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" load {}({}) - cache miss", cacheName, ebis.keySet());
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
@@ -795,7 +758,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Returns true if it managed to populate/load the single bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(key);
|
||||
if (cacheData == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
@@ -810,7 +772,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, cacheData, context);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
@@ -932,7 +893,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(key);
|
||||
if (existingData != null) {
|
||||
@@ -952,7 +912,6 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
cache.put(key, newData);
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = calculateNaturalKey(existingData);
|
||||
if (oldKey != null) {
|
||||
|
||||
@@ -262,10 +262,16 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
|
||||
/**
|
||||
* Return true if the target side has soft delete.
|
||||
*/
|
||||
@Override
|
||||
public boolean isTargetSoftDelete() {
|
||||
return targetDescriptor.isSoftDelete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSoftDeletePredicate(String tableAlias) {
|
||||
return targetDescriptor.getSoftDeletePredicate(tableAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if REFRESH should cascade.
|
||||
*/
|
||||
|
||||
@@ -56,7 +56,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
|
||||
}
|
||||
|
||||
private void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
|
||||
|
||||
if (cache) {
|
||||
query.setBeanCacheMode(CacheMode.ON);
|
||||
}
|
||||
@@ -70,7 +69,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
|
||||
}
|
||||
|
||||
protected void register(EntityBeanIntercept ebi) {
|
||||
|
||||
if (currentBuffer.isFull()) {
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
@@ -198,8 +196,10 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
|
||||
// lazy load property was a Many
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.hitCache) {
|
||||
if (list.isEmpty()) {
|
||||
// re-add to the batch and lazy load from DB skipping l2 cache
|
||||
list.add(ebi);
|
||||
} else if (context.hitCache) {
|
||||
Set<EntityBeanIntercept> hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
|
||||
list.removeAll(hits);
|
||||
if (list.isEmpty() || hits.contains(ebi)) {
|
||||
|
||||
@@ -81,8 +81,7 @@ public final class BatchControl {
|
||||
*/
|
||||
private int bufferMax;
|
||||
|
||||
private Queue earlyQueue;
|
||||
private Queue lateQueue;
|
||||
private Queue[] queues = new Queue[3];
|
||||
|
||||
/**
|
||||
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
|
||||
@@ -271,9 +270,10 @@ public final class BatchControl {
|
||||
}
|
||||
|
||||
private void flushBuffer(boolean reset) throws BatchedSqlException {
|
||||
flushQueue(queues[0]);
|
||||
flushInternal(reset);
|
||||
flushQueue(earlyQueue);
|
||||
flushQueue(lateQueue);
|
||||
flushQueue(queues[1]);
|
||||
flushQueue(queues[2]);
|
||||
}
|
||||
|
||||
private void flushQueue(Queue queue) throws BatchedSqlException {
|
||||
@@ -298,18 +298,7 @@ public final class BatchControl {
|
||||
// Nothing in queue to flush
|
||||
return;
|
||||
}
|
||||
|
||||
// convert entry map to array for sorting
|
||||
BatchedBeanHolder[] bsArray = getBeanHolderArray();
|
||||
// sort the entries by depth
|
||||
Arrays.sort(bsArray, depthComparator);
|
||||
|
||||
if (transaction.isLogSummary()) {
|
||||
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
|
||||
}
|
||||
for (BatchedBeanHolder beanHolder : bsArray) {
|
||||
beanHolder.executeNow();
|
||||
}
|
||||
executeAll();
|
||||
persistedBeans.clear();
|
||||
if (reset) {
|
||||
beanHoldMap.clear();
|
||||
@@ -323,6 +312,32 @@ public final class BatchControl {
|
||||
}
|
||||
}
|
||||
|
||||
private void executeAll() throws BatchedSqlException {
|
||||
do {
|
||||
// convert entry map to array for sorting
|
||||
BatchedBeanHolder[] bsArray = getBeanHolderArray();
|
||||
Arrays.sort(bsArray, depthComparator);
|
||||
if (transaction.isLogSummary()) {
|
||||
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
|
||||
}
|
||||
for (BatchedBeanHolder beanHolder : bsArray) {
|
||||
beanHolder.executeNow();
|
||||
}
|
||||
} while (!isBeanHoldersEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if all bean holders are empty.
|
||||
*/
|
||||
private boolean isBeanHoldersEmpty() {
|
||||
for (BatchedBeanHolder beanHolder : beanHoldMap.values()) {
|
||||
if (!beanHolder.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an entry for the given type description. The type description is
|
||||
* typically the bean class name (or table name for MapBeans).
|
||||
@@ -368,20 +383,11 @@ public final class BatchControl {
|
||||
/**
|
||||
* Add a SqlUpdate request to execute after flush.
|
||||
*/
|
||||
public void addToFlushQueue(PersistRequestUpdateSql request, boolean early) {
|
||||
if (early) {
|
||||
// add it to the early queue
|
||||
if (earlyQueue == null) {
|
||||
earlyQueue = new Queue();
|
||||
}
|
||||
earlyQueue.add(request);
|
||||
} else {
|
||||
// add it to the late queue
|
||||
if (lateQueue == null) {
|
||||
lateQueue = new Queue();
|
||||
}
|
||||
lateQueue.add(request);
|
||||
public void addToFlushQueue(PersistRequestUpdateSql request, int pos) {
|
||||
if (queues[pos] == null) {
|
||||
queues[pos] = new Queue();
|
||||
}
|
||||
queues[pos].add(request);
|
||||
}
|
||||
|
||||
private static class Queue {
|
||||
|
||||
@@ -47,6 +47,11 @@ class BatchedBeanHolder {
|
||||
*/
|
||||
private ArrayList<PersistRequest> deletes;
|
||||
|
||||
/**
|
||||
* The holder is empty
|
||||
*/
|
||||
private boolean empty = true;
|
||||
|
||||
/**
|
||||
* Create a new entry with a given type and depth.
|
||||
*/
|
||||
@@ -63,6 +68,13 @@ class BatchedBeanHolder {
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the BeanHolder is empty.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the persist requests in this entry.
|
||||
* <p>
|
||||
@@ -90,6 +102,7 @@ class BatchedBeanHolder {
|
||||
updates = new ArrayList<>();
|
||||
control.executeNow(bufferedUpdates);
|
||||
}
|
||||
empty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,9 +125,8 @@ class BatchedBeanHolder {
|
||||
* Add the request to the appropriate persist list.
|
||||
*/
|
||||
public int append(PersistRequestBean<?> request) {
|
||||
|
||||
empty = false;
|
||||
request.setBatched();
|
||||
|
||||
switch (request.getType()) {
|
||||
case INSERT:
|
||||
if (inserts == null) {
|
||||
|
||||
@@ -141,24 +141,17 @@ public final class DefaultPersister implements Persister {
|
||||
@Override
|
||||
public void executeOrQueue(SpiSqlUpdate update, SpiTransaction t, boolean queue) {
|
||||
if (queue) {
|
||||
addToFlushQueue(update, t, false);
|
||||
addToFlushQueue(update, t, 2);
|
||||
} else {
|
||||
executeSqlUpdate(update, t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t) {
|
||||
addToFlushQueue(update, t, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToFlushQueueLast(SpiSqlUpdate update, SpiTransaction t) {
|
||||
addToFlushQueue(update, t, false);
|
||||
}
|
||||
|
||||
private void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t, boolean early) {
|
||||
new PersistRequestUpdateSql(server, update, t, persistExecute).addToFlushQueue(early);
|
||||
/**
|
||||
* Add to the flush queue in position 0, 1 or 2.
|
||||
*/
|
||||
public void addToFlushQueue(SpiSqlUpdate update, SpiTransaction t, int pos) {
|
||||
new PersistRequestUpdateSql(server, update, t, persistExecute).addToFlushQueue(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -963,7 +956,7 @@ public final class DefaultPersister implements Persister {
|
||||
void deleteManyIntersection(EntityBean bean, BeanPropertyAssocMany<?> many, SpiTransaction t, boolean publish, boolean queue) {
|
||||
SpiSqlUpdate sqlDelete = deleteAllIntersection(bean, many, publish);
|
||||
if (queue) {
|
||||
addToFlushQueue(sqlDelete, t, true);
|
||||
addToFlushQueue(sqlDelete, t, 1);
|
||||
} else {
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
}
|
||||
@@ -1237,7 +1230,6 @@ public final class DefaultPersister implements Persister {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr,
|
||||
PersistRequest.Type type, int flags) {
|
||||
|
||||
// no delete requests come here
|
||||
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, flags);
|
||||
}
|
||||
@@ -1252,7 +1244,6 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <T> PersistRequestBean<T> createDeleteRequest(Object bean, Transaction t, PersistRequest.Type type, int flags) {
|
||||
|
||||
BeanManager<T> mgr = getBeanManager(bean);
|
||||
if (type == Type.DELETE_PERMANENT) {
|
||||
type = Type.DELETE;
|
||||
@@ -1281,7 +1272,6 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> BeanManager<T> getBeanManager(Object bean) {
|
||||
|
||||
BeanManager<T> mgr = (BeanManager<T>) beanDescriptorManager.getBeanManager(bean.getClass());
|
||||
if (mgr == null) {
|
||||
throw new PersistenceException(errNotRegistered(bean.getClass()));
|
||||
|
||||
+2
-2
@@ -56,7 +56,7 @@ class MergeNodeAssocManyToMany extends MergeNode {
|
||||
IntersectionTable intersectionTable = many.intersectionTable();
|
||||
|
||||
if (!deletions.isEmpty()) {
|
||||
transaction.flushBatch();
|
||||
transaction.flush();
|
||||
|
||||
SqlUpdate delete = intersectionTable.delete(server, false);
|
||||
for (EntityBean deletion : deletions) {
|
||||
@@ -67,7 +67,7 @@ class MergeNodeAssocManyToMany extends MergeNode {
|
||||
}
|
||||
|
||||
if (!additions.isEmpty()) {
|
||||
transaction.flushBatch();
|
||||
transaction.flush();
|
||||
|
||||
SqlUpdate insert = intersectionTable.insert(server, false);
|
||||
for (EntityBean addition : additions) {
|
||||
|
||||
@@ -49,7 +49,7 @@ abstract class SaveManyBase implements SaveMany {
|
||||
void preElementCollectionUpdate() {
|
||||
if (!insertedParent) {
|
||||
request.preElementCollectionUpdate();
|
||||
persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction);
|
||||
persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
private final boolean saveRecurseSkippable;
|
||||
private final DeleteMode deleteMode;
|
||||
private final boolean untouchedBeanCollection;
|
||||
private Collection<?> collection;
|
||||
private final Collection<?> collection;
|
||||
private int sortOrder;
|
||||
|
||||
SaveManyBeans(DefaultPersister persister, boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
|
||||
@@ -49,6 +49,7 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
this.saveRecurseSkippable = many.isSaveRecurseSkippable();
|
||||
this.deleteMode = targetDescriptor.isSoftDelete() ? DeleteMode.SOFT : DeleteMode.HARD;
|
||||
this.untouchedBeanCollection = untouchedBeanCollection();
|
||||
this.collection = cascade ? BeanCollectionUtil.getActualEntries(value) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +109,6 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
private void saveAssocManyDetails() {
|
||||
// check that the list is not null and if it is a BeanCollection
|
||||
// check that is has been populated (don't trigger lazy loading)
|
||||
collection = BeanCollectionUtil.getActualEntries(value);
|
||||
if (collection != null) {
|
||||
processDetails();
|
||||
}
|
||||
@@ -210,6 +210,18 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasNewOrDirtyBeans() {
|
||||
if (collection == null) {
|
||||
return false;
|
||||
}
|
||||
for (Object bean : collection) {
|
||||
if (bean instanceof EntityBean && ((EntityBean) bean)._ebean_getIntercept().isNewOrDirty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the Id values of the details to remove 'missing children' for stateless updates.
|
||||
*/
|
||||
@@ -235,9 +247,6 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
/**
|
||||
* Save the additions and removals from a ManyToMany collection as inserts
|
||||
* and deletes from the intersection table.
|
||||
* <p>
|
||||
* This is done via MapBeans.
|
||||
* </p>
|
||||
*/
|
||||
private void saveAssocManyIntersection() {
|
||||
if (value == null) {
|
||||
@@ -336,9 +345,14 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
}
|
||||
|
||||
private void removeAssocManyOrphans() {
|
||||
// check that the list is not null and if it is a BeanCollection
|
||||
// check that is has been populated (don't trigger lazy loading)
|
||||
if (value instanceof BeanCollection<?>) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (!(value instanceof BeanCollection<?>)) {
|
||||
// if (!insertedParent && cascade && hasNewOrDirtyBeans()) {
|
||||
// persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction, 0);
|
||||
// }
|
||||
} else {
|
||||
BeanCollection<?> c = (BeanCollection<?>) value;
|
||||
Set<?> modifyRemovals = c.getModifyRemovals();
|
||||
if (insertedParent) {
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ class SaveManyElementCollection extends SaveManyBase {
|
||||
final SpiSqlUpdate sqlInsert = proto.copy();
|
||||
sqlInsert.setParameter(parentId);
|
||||
many.bindElementValue(sqlInsert, value);
|
||||
persister.addToFlushQueueLast(sqlInsert, transaction);
|
||||
persister.addToFlushQueue(sqlInsert, transaction, 2);
|
||||
}
|
||||
resetModifyState();
|
||||
postElementCollectionUpdate();
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ class SaveManyElementCollectionMap extends SaveManyBase {
|
||||
sqlInsert.setParameter(parentId);
|
||||
sqlInsert.setParameter(entry.getKey());
|
||||
many.bindElementValue(sqlInsert, entry.getValue());
|
||||
persister.addToFlushQueueLast(sqlInsert, transaction);
|
||||
persister.addToFlushQueue(sqlInsert, transaction, 2);
|
||||
}
|
||||
resetModifyState();
|
||||
postElementCollectionUpdate();
|
||||
|
||||
@@ -571,8 +571,8 @@ class CQueryBuilder {
|
||||
private final CQueryPredicates predicates;
|
||||
private final SqlTree select;
|
||||
private final boolean updateStatement;
|
||||
|
||||
private final boolean distinct;
|
||||
private final boolean countSingleAttribute;
|
||||
private final String dbOrderBy;
|
||||
private boolean useSqlLimiter;
|
||||
private boolean hasWhere;
|
||||
@@ -590,6 +590,7 @@ class CQueryBuilder {
|
||||
this.updateStatement = updateStatement;
|
||||
this.distinct = query.isDistinct() || select.isSqlDistinct();
|
||||
this.dbOrderBy = predicates.getDbOrderBy();
|
||||
this.countSingleAttribute = query.isCountDistinct() && query.isSingleAttribute();
|
||||
}
|
||||
|
||||
private void appendSelect() {
|
||||
@@ -601,8 +602,13 @@ class CQueryBuilder {
|
||||
if (!useSqlLimiter) {
|
||||
appendSelectDistinct();
|
||||
}
|
||||
if (query.isCountDistinct() && query.isSingleAttribute()) {
|
||||
sb.append("r1.attribute_, count(*) from (select ").append(select.getSelectSql()).append(" as attribute_");
|
||||
if (countSingleAttribute) {
|
||||
sb.append("r1.attribute_, count(*) from (select ");
|
||||
if (distinct) {
|
||||
sb.append("distinct t0.");
|
||||
sb.append(request.getBeanDescriptor().getIdProperty().getDbColumn()).append(", ");
|
||||
}
|
||||
sb.append(select.getSelectSql()).append(" as attribute_");
|
||||
} else {
|
||||
sb.append(select.getSelectSql());
|
||||
}
|
||||
@@ -621,7 +627,7 @@ class CQueryBuilder {
|
||||
|
||||
private void appendSelectDistinct() {
|
||||
sb.append("select ");
|
||||
if (distinct) {
|
||||
if (distinct && !countSingleAttribute) {
|
||||
if (request.isInlineCountDistinct()) {
|
||||
sb.append("count(");
|
||||
}
|
||||
@@ -730,7 +736,7 @@ class CQueryBuilder {
|
||||
sb.append(" order by ").append(dbOrderBy);
|
||||
}
|
||||
|
||||
if (query.isCountDistinct() && query.isSingleAttribute()) {
|
||||
if (countSingleAttribute) {
|
||||
sb.append(") r1 group by r1.attribute_");
|
||||
sb.append(toSql(query.getCountDistinctOrder()));
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
t.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,4 +36,13 @@ public interface STreePropertyAssoc extends STreeProperty {
|
||||
*/
|
||||
void setValue(EntityBean parentBean, Object contextBean);
|
||||
|
||||
/**
|
||||
* Return true if the associated type has soft delete.
|
||||
*/
|
||||
boolean isTargetSoftDelete();
|
||||
|
||||
/**
|
||||
* Return the soft delete predicate.
|
||||
*/
|
||||
String getSoftDeletePredicate(String tableAlias);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ public final class SqlTreeBuilder {
|
||||
* Construct for RawSql query.
|
||||
*/
|
||||
SqlTreeBuilder(OrmQueryRequest<?> request, CQueryPredicates predicates, OrmQueryDetail queryDetail, boolean rawNoId) {
|
||||
|
||||
this.rawSql = true;
|
||||
this.desc = request.getBeanDescriptor();
|
||||
this.rawNoId = rawNoId;
|
||||
@@ -97,7 +96,6 @@ public final class SqlTreeBuilder {
|
||||
* to the root node.
|
||||
*/
|
||||
SqlTreeBuilder(String columnAliasPrefix, CQueryBuilder builder, OrmQueryRequest<?> request, CQueryPredicates predicates) {
|
||||
|
||||
this.rawSql = false;
|
||||
this.rawNoId = false;
|
||||
this.desc = request.getBeanDescriptor();
|
||||
@@ -108,11 +106,9 @@ public final class SqlTreeBuilder {
|
||||
this.includeJoin = query.getM2mIncludeJoin();
|
||||
this.manyWhereJoins = query.getManyWhereJoins();
|
||||
this.queryDetail = query.getDetail();
|
||||
|
||||
this.predicates = predicates;
|
||||
this.alias = new SqlTreeAlias(request.getBaseTableAlias(), temporalMode);
|
||||
this.distinctOnPlatform = builder.isPlatformDistinctOn();
|
||||
|
||||
String fromForUpdate = builder.fromForUpdate(query);
|
||||
CQueryHistorySupport historySupport = builder.getHistorySupport(query);
|
||||
CQueryDraftSupport draftSupport = builder.getDraftSupport(query);
|
||||
@@ -124,10 +120,8 @@ public final class SqlTreeBuilder {
|
||||
* Build based on the includes and using the BeanJoinTree.
|
||||
*/
|
||||
public SqlTree build() {
|
||||
|
||||
// build the appropriate chain of SelectAdapter's
|
||||
buildRoot(desc);
|
||||
|
||||
// build the actual String
|
||||
String distinctOn = null;
|
||||
String selectSql = null;
|
||||
@@ -149,7 +143,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private String buildSelectClause() {
|
||||
|
||||
if (rawSql) {
|
||||
return "Not Used";
|
||||
}
|
||||
@@ -158,7 +151,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private String buildGroupByClause() {
|
||||
|
||||
if (rawSql || !rootNode.isAggregation()) {
|
||||
return null;
|
||||
}
|
||||
@@ -168,7 +160,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private String buildDistinctOn() {
|
||||
|
||||
if (rawSql || !distinctOnPlatform || !sqlDistinct || Type.COUNT == query.getType()) {
|
||||
return null;
|
||||
}
|
||||
@@ -207,7 +198,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private String buildWhereClause() {
|
||||
|
||||
if (rawSql) {
|
||||
return "Not Used";
|
||||
}
|
||||
@@ -216,7 +206,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private String buildFromClause() {
|
||||
|
||||
if (rawSql) {
|
||||
return "Not Used";
|
||||
}
|
||||
@@ -225,17 +214,13 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private void buildRoot(STreeType desc) {
|
||||
|
||||
rootNode = buildSelectChain(null, null, desc, null);
|
||||
|
||||
if (!rawSql) {
|
||||
alias.addJoin(queryDetail.getFetchPaths(), desc);
|
||||
alias.addJoin(predicates.getPredicateIncludes(), desc);
|
||||
alias.addManyWhereJoins(manyWhereJoins.getPropertyNames());
|
||||
|
||||
// build set of table alias
|
||||
alias.buildAlias();
|
||||
|
||||
predicates.parseTableAlias(alias);
|
||||
}
|
||||
}
|
||||
@@ -246,9 +231,7 @@ public final class SqlTreeBuilder {
|
||||
*/
|
||||
private SqlTreeNode buildSelectChain(String prefix, STreePropertyAssoc prop,
|
||||
STreeType desc, List<SqlTreeNode> joinList) {
|
||||
|
||||
List<SqlTreeNode> myJoinList = new ArrayList<>();
|
||||
|
||||
List<STreePropertyAssocOne> extraProps = new ArrayList<>();
|
||||
for (STreePropertyAssocOne one : desc.propsOne()) {
|
||||
String propPrefix = SplitName.add(prefix, one.getName());
|
||||
@@ -295,7 +278,6 @@ public final class SqlTreeBuilder {
|
||||
* </p>
|
||||
*/
|
||||
private void addManyWhereJoins(List<SqlTreeNode> myJoinList) {
|
||||
|
||||
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
|
||||
for (PropertyJoin joinProp : includes) {
|
||||
STreePropertyAssoc beanProperty = (STreePropertyAssoc) desc.findPropertyFromPath(joinProp.getProperty());
|
||||
@@ -311,7 +293,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private SqlTreeNode buildNode(String prefix, STreePropertyAssoc prop, STreeType desc, List<SqlTreeNode> myList, SqlTreeProperties props) {
|
||||
|
||||
if (prefix == null) {
|
||||
buildExtraJoins(desc, myList);
|
||||
|
||||
@@ -340,13 +321,10 @@ public final class SqlTreeBuilder {
|
||||
* already in select clause.
|
||||
*/
|
||||
private void buildExtraJoins(STreeType desc, List<SqlTreeNode> myList) {
|
||||
|
||||
if (rawSql) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> predicateIncludes = predicates.getPredicateIncludes();
|
||||
|
||||
if (predicateIncludes == null) {
|
||||
return;
|
||||
}
|
||||
@@ -362,8 +340,7 @@ public final class SqlTreeBuilder {
|
||||
|
||||
// look for predicateIncludes that are not in selectIncludes and add
|
||||
// them as extra joins to the query
|
||||
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes);
|
||||
|
||||
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes, temporalMode);
|
||||
Collection<SqlTreeNodeExtraJoin> extraJoins = extraJoinDistill.getExtraJoinRootNodes();
|
||||
if (!extraJoins.isEmpty()) {
|
||||
// add extra joins required to support predicates
|
||||
@@ -389,7 +366,6 @@ public final class SqlTreeBuilder {
|
||||
* </p>
|
||||
*/
|
||||
private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) {
|
||||
|
||||
STreeProperty p = desc.findProperty(propName);
|
||||
if (p == null) {
|
||||
logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it.");
|
||||
@@ -402,13 +378,10 @@ public final class SqlTreeBuilder {
|
||||
p = ((STreePropertyAssoc) p).target().findProperty(name);
|
||||
}
|
||||
}
|
||||
|
||||
selectProps.add(p);
|
||||
}
|
||||
|
||||
private void addProperty(SqlTreeProperties selectProps, STreeType desc,
|
||||
OrmQueryProperties queryProps, String propName) {
|
||||
|
||||
private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQueryProperties queryProps, String propName) {
|
||||
if (subQuery) {
|
||||
addPropertyToSubQuery(selectProps, desc, propName);
|
||||
return;
|
||||
@@ -471,7 +444,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private SqlTreeProperties getBaseSelectPartial(STreeType desc, OrmQueryProperties queryProps) {
|
||||
|
||||
SqlTreeProperties selectProps = new SqlTreeProperties();
|
||||
// add properties in the order in which they appear
|
||||
// in the query. Gives predictable sql/properties for
|
||||
@@ -502,7 +474,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
|
||||
private SqlTreeProperties getBaseSelect(STreeType desc, OrmQueryProperties queryProps) {
|
||||
|
||||
boolean partial = queryProps != null && !queryProps.allProperties();
|
||||
if (partial) {
|
||||
return getBaseSelectPartial(desc, queryProps);
|
||||
@@ -541,13 +512,10 @@ public final class SqlTreeBuilder {
|
||||
* Return true if this many node should be included in the query.
|
||||
*/
|
||||
private boolean isIncludeMany(String propName, STreePropertyAssocMany manyProp) {
|
||||
|
||||
if (queryDetail.isJoinsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (queryDetail.includesPath(propName)) {
|
||||
|
||||
if (manyProperty != null) {
|
||||
// only one many associated allowed to be included in fetch
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -555,7 +523,6 @@ public final class SqlTreeBuilder {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
manyProperty = manyProp;
|
||||
return true;
|
||||
}
|
||||
@@ -571,7 +538,6 @@ public final class SqlTreeBuilder {
|
||||
* </p>
|
||||
*/
|
||||
private boolean isIncludeBean(String prefix) {
|
||||
|
||||
if (queryDetail.includesPath(prefix)) {
|
||||
// explicitly included
|
||||
String[] splitNames = SplitName.split(prefix);
|
||||
@@ -592,26 +558,20 @@ public final class SqlTreeBuilder {
|
||||
*/
|
||||
private static class IncludesDistiller {
|
||||
|
||||
private final STreeType desc;
|
||||
private final Set<String> selectIncludes;
|
||||
private final Set<String> predicateIncludes;
|
||||
private final SpiQuery.TemporalMode temporalMode;
|
||||
|
||||
/**
|
||||
* Contains the 'root' extra joins. We only return the roots back.
|
||||
*/
|
||||
private final Map<String, SqlTreeNodeExtraJoin> joinRegister = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Register of all the extra join nodes.
|
||||
*/
|
||||
private final Map<String, SqlTreeNodeExtraJoin> rootRegister = new HashMap<>();
|
||||
|
||||
private final STreeType desc;
|
||||
|
||||
private IncludesDistiller(STreeType desc, Set<String> selectIncludes,
|
||||
Set<String> predicateIncludes) {
|
||||
Set<String> predicateIncludes, SpiQuery.TemporalMode temporalMode) {
|
||||
this.desc = desc;
|
||||
this.selectIncludes = selectIncludes;
|
||||
this.predicateIncludes = predicateIncludes;
|
||||
this.temporalMode = temporalMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -623,26 +583,21 @@ public final class SqlTreeBuilder {
|
||||
* </p>
|
||||
*/
|
||||
private Collection<SqlTreeNodeExtraJoin> getExtraJoinRootNodes() {
|
||||
|
||||
String[] extras = findExtras();
|
||||
if (extras.length == 0) {
|
||||
return rootRegister.values();
|
||||
}
|
||||
|
||||
// sort so we process only getting the leaves
|
||||
// excluding nodes between root and the leaf
|
||||
Arrays.sort(extras);
|
||||
|
||||
// reverse order so get the leaves first...
|
||||
for (String extra : extras) {
|
||||
createExtraJoin(extra);
|
||||
}
|
||||
|
||||
return rootRegister.values();
|
||||
}
|
||||
|
||||
private void createExtraJoin(String includeProp) {
|
||||
|
||||
SqlTreeNodeExtraJoin extraJoin = createJoinLeaf(includeProp);
|
||||
if (extraJoin != null) {
|
||||
// add the extra join...
|
||||
@@ -650,7 +605,6 @@ public final class SqlTreeBuilder {
|
||||
// find root of this extra join... linking back to the
|
||||
// parents (creating the tree) as it goes.
|
||||
SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin);
|
||||
|
||||
// register the root because these are the only ones we
|
||||
// return back.
|
||||
rootRegister.put(root.getName(), root);
|
||||
@@ -661,12 +615,11 @@ public final class SqlTreeBuilder {
|
||||
* Create a SqlTreeNodeExtraJoin, register and return it.
|
||||
*/
|
||||
private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) {
|
||||
|
||||
ExtraJoin extra = desc.extraJoin(propertyName);
|
||||
if (extra == null) {
|
||||
return null;
|
||||
} else {
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany());
|
||||
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany(), temporalMode);
|
||||
joinRegister.put(propertyName, extraJoin);
|
||||
return extraJoin;
|
||||
}
|
||||
@@ -713,9 +666,7 @@ public final class SqlTreeBuilder {
|
||||
* by the select.
|
||||
*/
|
||||
private String[] findExtras() {
|
||||
|
||||
List<String> extras = new ArrayList<>();
|
||||
|
||||
for (String predProp : predicateIncludes) {
|
||||
if (!selectIncludes.contains(predProp)) {
|
||||
extras.add(predProp);
|
||||
|
||||
@@ -24,19 +24,17 @@ import java.util.Set;
|
||||
class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
|
||||
private final STreePropertyAssoc assocBeanProperty;
|
||||
|
||||
private final SpiQuery.TemporalMode temporalMode;
|
||||
private final String prefix;
|
||||
|
||||
private final boolean manyJoin;
|
||||
|
||||
private final boolean pathContainsMany;
|
||||
|
||||
private List<SqlTreeNodeExtraJoin> children;
|
||||
|
||||
SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany) {
|
||||
SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany, SpiQuery.TemporalMode temporalMode) {
|
||||
this.prefix = prefix;
|
||||
this.assocBeanProperty = assocBeanProperty;
|
||||
this.pathContainsMany = pathContainsMany;
|
||||
this.temporalMode = temporalMode;
|
||||
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
|
||||
}
|
||||
|
||||
@@ -113,9 +111,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
|
||||
|
||||
boolean manyToMany = false;
|
||||
|
||||
if (assocBeanProperty instanceof STreePropertyAssocMany) {
|
||||
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) assocBeanProperty;
|
||||
if (manyProp.hasJoinTable()) {
|
||||
@@ -144,15 +140,16 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
assocBeanProperty.appendFrom(ctx, joinType);
|
||||
}
|
||||
joinType = assocBeanProperty.addJoin(joinType, prefix, ctx);
|
||||
if (assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) {
|
||||
ctx.append(" and ").append(assocBeanProperty.getSoftDeletePredicate(ctx.getTableAlias(prefix)));
|
||||
}
|
||||
}
|
||||
|
||||
if (children != null) {
|
||||
|
||||
if (manyJoin || pathContainsMany) {
|
||||
// if AUTO then make all descendants use OUTER JOIN
|
||||
joinType = joinType.autoToOuter();
|
||||
}
|
||||
|
||||
for (SqlTreeNodeExtraJoin child : children) {
|
||||
child.appendFrom(ctx, joinType);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package io.ebeaninternal.server.querydefn;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.UpdateQuery;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
/**
|
||||
* Default implementation of UpdateQuery.
|
||||
@@ -10,23 +13,30 @@ import io.ebean.UpdateQuery;
|
||||
public class DefaultUpdateQuery<T> implements UpdateQuery<T> {
|
||||
|
||||
private final OrmUpdateProperties values = new OrmUpdateProperties();
|
||||
|
||||
private final DefaultOrmQuery<T> query;
|
||||
private final BeanDescriptor<T> descriptor;
|
||||
|
||||
public DefaultUpdateQuery(DefaultOrmQuery<T> query) {
|
||||
this.query = query;
|
||||
this.descriptor = query.getBeanDescriptor();
|
||||
query.setUpdateProperties(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> set(String property, Object value) {
|
||||
values.set(property, value);
|
||||
if (value == null) {
|
||||
values.setNull(property);
|
||||
} else {
|
||||
final BeanProperty beanProperty = descriptor.getBeanProperty(property);
|
||||
final ScalarType<Object> scalarType = (beanProperty == null) ? null: beanProperty.getScalarType();
|
||||
values.set(property, value, scalarType);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> setNull(String property) {
|
||||
values.set(property, null);
|
||||
values.setNull(property);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
+14
-11
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.server.deploy.DeployParser;
|
||||
import io.ebeaninternal.server.persist.Binder;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
@@ -53,9 +54,11 @@ public class OrmUpdateProperties {
|
||||
private static class SimpleValue extends Value {
|
||||
|
||||
final Object value;
|
||||
final ScalarType<Object> scalarType;
|
||||
|
||||
SimpleValue(Object value) {
|
||||
SimpleValue(Object value, ScalarType<Object> scalarType) {
|
||||
this.value = value;
|
||||
this.scalarType = scalarType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,7 +73,11 @@ public class OrmUpdateProperties {
|
||||
|
||||
@Override
|
||||
public void bind(Binder binder, DataBind dataBind) throws SQLException {
|
||||
binder.bindObject(dataBind, value);
|
||||
if (scalarType != null) {
|
||||
scalarType.bind(dataBind, value);
|
||||
} else {
|
||||
binder.bindObject(dataBind, value);
|
||||
}
|
||||
dataBind.append(value).append(",");
|
||||
}
|
||||
}
|
||||
@@ -115,16 +122,12 @@ public class OrmUpdateProperties {
|
||||
*/
|
||||
private final LinkedHashMap<String, Value> values = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Normal set property.
|
||||
*/
|
||||
public void set(String propertyName, Object value) {
|
||||
if (value == null) {
|
||||
values.put(propertyName, NULL_VALUE);
|
||||
public void set(String propertyName, Object value, ScalarType<Object> scalarType) {
|
||||
values.put(propertyName, new SimpleValue(value, scalarType));
|
||||
}
|
||||
|
||||
} else {
|
||||
values.put(propertyName, new SimpleValue(value));
|
||||
}
|
||||
public void setNull(String propertyName) {
|
||||
values.put(propertyName, NULL_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+15
@@ -586,6 +586,21 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCommit() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommit() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRollback(Throwable cause) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the transaction is active.
|
||||
*/
|
||||
|
||||
@@ -373,6 +373,9 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
|
||||
private void firePreCommit() {
|
||||
withEachCallback(TransactionCallback::preCommit);
|
||||
if (changeLogHolder != null) {
|
||||
changeLogHolder.preCommit();
|
||||
}
|
||||
}
|
||||
|
||||
private void firePostCommit() {
|
||||
@@ -981,14 +984,23 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
* Batch flush, jdbc commit, trigger registered TransactionCallbacks, notify l2 cache etc.
|
||||
*/
|
||||
private void flushCommitAndNotify() throws SQLException {
|
||||
internalBatchFlush();
|
||||
firePreCommit();
|
||||
// only performCommit can throw an exception
|
||||
preCommit();
|
||||
performCommit();
|
||||
postCommit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommit() {
|
||||
firePostCommit();
|
||||
notifyCommit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCommit() {
|
||||
internalBatchFlush();
|
||||
firePreCommit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a commit, fire callbacks and notify l2 cache etc.
|
||||
* <p>
|
||||
@@ -1132,11 +1144,16 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
|
||||
} finally {
|
||||
// these will not throw an exception
|
||||
firePostRollback();
|
||||
notifyRollback(cause);
|
||||
postRollback(cause);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRollback(Throwable cause) {
|
||||
firePostRollback();
|
||||
notifyRollback(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transaction is active then perform rollback.
|
||||
*/
|
||||
|
||||
@@ -13,9 +13,9 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
|
||||
private final UserTransaction userTransaction;
|
||||
|
||||
private boolean commmitted;
|
||||
private final boolean newTransaction;
|
||||
|
||||
private boolean newTransaction;
|
||||
private boolean committed;
|
||||
|
||||
/**
|
||||
* Create the JtaTransaction.
|
||||
@@ -41,7 +41,6 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
if (connection.getAutoCommit()) {
|
||||
connection.setAutoCommit(false);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
@@ -52,7 +51,7 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
*/
|
||||
@Override
|
||||
public void commit() {
|
||||
if (commmitted) {
|
||||
if (committed) {
|
||||
throw new PersistenceException("This transaction has already been committed.");
|
||||
}
|
||||
try {
|
||||
@@ -60,14 +59,14 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
if (newTransaction) {
|
||||
userTransaction.commit();
|
||||
}
|
||||
notifyCommit();
|
||||
postCommit();
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
commmitted = true;
|
||||
committed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -80,7 +79,7 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
*/
|
||||
@Override
|
||||
public void rollback(Throwable e) {
|
||||
if (!commmitted) {
|
||||
if (!committed) {
|
||||
try {
|
||||
try {
|
||||
if (userTransaction != null) {
|
||||
@@ -90,7 +89,7 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
userTransaction.setRollbackOnly();
|
||||
}
|
||||
}
|
||||
notifyRollback(e);
|
||||
postRollback(e);
|
||||
} finally {
|
||||
closeConnection();
|
||||
}
|
||||
@@ -98,7 +97,6 @@ public class JtaTransaction extends JdbcTransaction {
|
||||
throw new PersistenceException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-5
@@ -190,19 +190,17 @@ public class JtaTransactionManager implements ExternalTransactionManager {
|
||||
|
||||
@Override
|
||||
public void beforeCompletion() {
|
||||
// Future note: for JPA2 locking we will
|
||||
// have beforeCommit events to fire
|
||||
transaction.preCommit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
|
||||
switch (status) {
|
||||
case Status.STATUS_COMMITTED:
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Jta Txn [" + transaction.getId() + "] committed");
|
||||
}
|
||||
transactionManager.notifyOfCommit(transaction);
|
||||
transaction.postCommit();
|
||||
// Remove this transaction object as it is completed
|
||||
transactionManager.scope().clearExternal();
|
||||
break;
|
||||
@@ -211,7 +209,7 @@ public class JtaTransactionManager implements ExternalTransactionManager {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Jta Txn [" + transaction.getId() + "] rollback");
|
||||
}
|
||||
transactionManager.notifyOfRollback(transaction, null);
|
||||
transaction.postRollback(null);
|
||||
// Remove this transaction object as it is completed
|
||||
transactionManager.scope().clearExternal();
|
||||
break;
|
||||
|
||||
@@ -88,6 +88,21 @@ class NoTransaction implements SpiTransaction {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preCommit() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postCommit() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postRollback(Throwable cause) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return null;
|
||||
|
||||
@@ -56,20 +56,31 @@ public class TChangeLogHolder {
|
||||
* Add a bean change to the change set.
|
||||
*/
|
||||
public void addBeanChange(BeanChange change) {
|
||||
|
||||
changes.addBeanChange(change);
|
||||
if (++count >= batchSize) {
|
||||
// we hit the batch size so send what we have knowing
|
||||
// that the transaction has not completed yet and
|
||||
// reset the changes and count
|
||||
owner.sendChangeLog(changes);
|
||||
changes = new ChangeSet(transactionId, ++batchId);
|
||||
count = 0;
|
||||
sendChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChanges() {
|
||||
owner.sendChangeLog(changes);
|
||||
changes = new ChangeSet(transactionId, ++batchId);
|
||||
count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* On post commit send the changes we have collected.
|
||||
* Send the changes held prior to transaction commit.
|
||||
*/
|
||||
public void preCommit() {
|
||||
sendChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* On post commit send the changes we have collected. This should be
|
||||
* only the COMMITTED state and with all changes sent prior to commit.
|
||||
*/
|
||||
public void postCommit() {
|
||||
changes.setTxnState(TxnState.COMMITTED);
|
||||
|
||||
+1
-1
@@ -634,7 +634,7 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
public ScopedTransaction externalBeginTransaction(SpiTransaction transaction, TxScope txScope) {
|
||||
ScopedTransaction scopedTxn = new ScopedTransaction(scopeManager);
|
||||
scopedTxn.push(new ScopeTrans(rollbackOnChecked, false, transaction, txScope));
|
||||
scopeManager.set(scopedTxn);
|
||||
scopeManager.replace(scopedTxn);
|
||||
return scopedTxn;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.tests.batchinsert;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.DtoQuery2Test;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.Transaction;
|
||||
@@ -105,6 +107,41 @@ public class TestBatchInsertFlush extends BaseTestCase {
|
||||
assertSql(sql.get(0)).contains("select count(*)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional(batchSize = 20)
|
||||
public void transactional_flushOnSqlQuery() {
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
DB.save(new EBasicVer("b1"));
|
||||
DB.save(new EBasicVer("b2"));
|
||||
|
||||
// trigger JDBC batch by default
|
||||
DB.sqlQuery("select count(*) from e_basicver")
|
||||
.mapToScalar(Integer.class)
|
||||
.findOne();
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertSql(sql.get(0)).contains("insert into e_basicver");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional(batchSize = 20)
|
||||
public void transactional_flushOnDtoQuery() {
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
DB.save(new EBasicVer("b1"));
|
||||
DB.save(new EBasicVer("b2"));
|
||||
|
||||
// trigger JDBC batch by default
|
||||
DB.findDto(DtoQuery2Test.DCust.class, "select id, name from o_customer")
|
||||
.findList();
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
assertSql(sql.get(0)).contains("insert into e_basicver");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional(batch = PersistBatch.ALL)
|
||||
public void transactional_flushOnQuery() {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package org.tests.cache;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.tests.model.cache.M2MCacheChild;
|
||||
import org.tests.model.cache.M2MCacheMaster;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
|
||||
public class TestM2MCache extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testM2MWithCache() throws Exception {
|
||||
M2MCacheChild cld = new M2MCacheChild();
|
||||
cld.setName("test");
|
||||
cld.setId(1);
|
||||
DB.save(cld);
|
||||
|
||||
M2MCacheMaster cfg = new M2MCacheMaster();
|
||||
cfg.setId(42);
|
||||
cfg.getSet1().add(cld);
|
||||
cfg.getSet2().add(cld);
|
||||
DB.save(cfg);
|
||||
|
||||
// find master and access set1 + set2.
|
||||
// lazy load something from set1
|
||||
M2MCacheMaster cfg1 = DB.find(M2MCacheMaster.class, 42);
|
||||
|
||||
cfg1.getSet1().size();
|
||||
cfg1.getSet2().size();
|
||||
assertThat(cfg1.getSet1().iterator().next().getName()).isEqualTo("test");
|
||||
|
||||
// do it again
|
||||
cfg1 = DB.find(M2MCacheMaster.class, 42);
|
||||
|
||||
cfg1.getSet1().size();
|
||||
cfg1.getSet2().size();
|
||||
|
||||
assertThat(cfg1.getSet1().iterator().next().getName()).isEqualTo("test");
|
||||
// do it again
|
||||
cfg1 = DB.find(M2MCacheMaster.class, 42);
|
||||
|
||||
cfg1.getSet1().size();
|
||||
cfg1.getSet2().size();
|
||||
|
||||
assertThat(cfg1.getSet1().iterator().next().getName()).isEqualTo("test");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testM2MWithCacheMinimal() throws Exception {
|
||||
M2MCacheChild cld = new M2MCacheChild();
|
||||
cld.setName("test");
|
||||
cld.setId(2);
|
||||
DB.save(cld);
|
||||
|
||||
M2MCacheMaster cfg = new M2MCacheMaster();
|
||||
cfg.setId(43);
|
||||
cfg.getSet1().add(cld);
|
||||
cfg.getSet2().add(cld);
|
||||
DB.save(cfg);
|
||||
|
||||
DB.find(M2MCacheMaster.class, 43);
|
||||
|
||||
M2MCacheMaster cfg1 = DB.find(M2MCacheMaster.class, 43);
|
||||
cfg1.getSet2().size();
|
||||
|
||||
cfg1 = DB.find(M2MCacheMaster.class, 43);
|
||||
|
||||
cfg1.getSet1().size();
|
||||
cfg1.getSet2().size();
|
||||
|
||||
assertThat(cfg1.getSet1().iterator().next().getName()).isEqualTo("test");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,7 @@ public class COOne {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<COOneMany> children) {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestDeleteO2MOrphans extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
final long id = setup();
|
||||
// act
|
||||
setNewChildren(id);
|
||||
|
||||
// assert
|
||||
COOne check = findById(id);
|
||||
//FIXME #2127 #2141: assertThat(check.getChildren()).hasSize(2);
|
||||
assertThat(check.getChildren()).hasSize(4);
|
||||
DB.delete(check);
|
||||
}
|
||||
|
||||
private COOne findById(long id) {
|
||||
return DB.find(COOne.class).where().idEq(id).findOne();
|
||||
}
|
||||
|
||||
private void setNewChildren(long id) {
|
||||
COOne found = findById(id);
|
||||
found.setChildren(createManies("M3", "M4"));
|
||||
DB.update(found);
|
||||
}
|
||||
|
||||
private long setup() {
|
||||
COOne company = new COOne("P0");
|
||||
company.setChildren(createManies("M1", "M2"));
|
||||
DB.insert(company);
|
||||
return company.getId();
|
||||
}
|
||||
|
||||
private List<COOneMany> createManies(String name1, String name2) {
|
||||
COOneMany employee1 = new COOneMany(name1);
|
||||
COOneMany employee2 = new COOneMany(name2);
|
||||
List<COOneMany> employees = new ArrayList<>();
|
||||
employees.add(employee1);
|
||||
employees.add(employee2);
|
||||
return employees;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestOnlyKillOrphans extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
final COOne one = setup();
|
||||
|
||||
assertThat(one.getChildren()).hasSize(2);
|
||||
|
||||
CORoot root = new CORoot("P1", one);
|
||||
DB.insert(root);
|
||||
|
||||
// assert
|
||||
CORoot check = DB.find(CORoot.class, root.getId());
|
||||
assertThat(check.getOne().getChildren()).hasSize(2);
|
||||
DB.delete(check);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
final COOne one = setup();
|
||||
|
||||
assertThat(one.getChildren()).hasSize(2);
|
||||
|
||||
one.getChildren().add(new COOneMany("_M1"));
|
||||
|
||||
DB.save(one);
|
||||
|
||||
// assert
|
||||
COOne check = DB.find(COOne.class, one.getId());
|
||||
assertThat(check.getChildren().stream().map(it -> it.getName()).sorted().collect(Collectors.toList()))
|
||||
.isEqualTo(Stream.of("M1", "M2", "_M1").sorted().collect(Collectors.toList()));
|
||||
DB.delete(check);
|
||||
}
|
||||
|
||||
private COOne setup() {
|
||||
COOne one = new COOne("P0");
|
||||
one.setChildren(createManies("M1", "M2"));
|
||||
DB.insert(one);
|
||||
return one;
|
||||
}
|
||||
|
||||
private List<COOneMany> createManies(String... names) {
|
||||
List<COOneMany> manies = new ArrayList<>();
|
||||
for (String name : names) {
|
||||
manies.add(new COOneMany(name));
|
||||
}
|
||||
return manies;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,15 @@ import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebean.event.changelog.ChangeType;
|
||||
import io.ebean.event.changelog.TxnState;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.EBasicChangeLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -43,6 +47,13 @@ public class TestChangeLog extends BaseTestCase {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
private BeanChange firstChange() {
|
||||
ChangeSet changeSet0 = changeLogListener.changes.get(0);
|
||||
BeanChange change = changeSet0.getChanges().get(0);
|
||||
changeLogListener.changes.clear();
|
||||
return change;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
@@ -50,8 +61,13 @@ public class TestChangeLog extends BaseTestCase {
|
||||
bean.setName("logBean");
|
||||
bean.setShortDescription("hello");
|
||||
server.save(bean);
|
||||
BeanChange change = changeLogListener.changes.getChanges().get(0);
|
||||
|
||||
final List<ChangeSet> allChanges = changeLogListener.changes;
|
||||
assertThat(allChanges).hasSize(2);
|
||||
assertThat(allChanges.get(0).getTxnState()).isEqualTo(TxnState.IN_PROGRESS);
|
||||
assertThat(allChanges.get(1).getTxnState()).isEqualTo(TxnState.COMMITTED);
|
||||
|
||||
BeanChange change = firstChange();
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT);
|
||||
assertThat(change.getData())
|
||||
.contains("\"name\":\"logBean\"")
|
||||
@@ -61,8 +77,7 @@ public class TestChangeLog extends BaseTestCase {
|
||||
bean.setName("ChangedName");
|
||||
server.save(bean);
|
||||
|
||||
change = changeLogListener.changes.getChanges().get(0);
|
||||
|
||||
change = firstChange();
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE);
|
||||
assertThat(change.getOldData()).contains("\"name\":\"logBean\"");
|
||||
assertThat(change.getData()).contains("\"name\":\"ChangedName\"");
|
||||
@@ -70,14 +85,11 @@ public class TestChangeLog extends BaseTestCase {
|
||||
|
||||
server.delete(bean);
|
||||
|
||||
change = changeLogListener.changes.getChanges().get(0);
|
||||
|
||||
change = firstChange();
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE);
|
||||
assertThat(change.getData()).isNull();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWithNull() {
|
||||
|
||||
@@ -85,7 +97,7 @@ public class TestChangeLog extends BaseTestCase {
|
||||
bean.setName(null);
|
||||
bean.setShortDescription("hello");
|
||||
server.save(bean);
|
||||
BeanChange change = changeLogListener.changes.getChanges().get(0);
|
||||
BeanChange change = firstChange();
|
||||
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT);
|
||||
assertThat(change.getData())
|
||||
@@ -99,7 +111,7 @@ public class TestChangeLog extends BaseTestCase {
|
||||
bean.setShortDescription("hello");
|
||||
server.save(bean);
|
||||
|
||||
change = changeLogListener.changes.getChanges().get(0);
|
||||
change = firstChange();
|
||||
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE);
|
||||
assertThat(change.getOldData())
|
||||
@@ -113,11 +125,10 @@ public class TestChangeLog extends BaseTestCase {
|
||||
|
||||
server.delete(bean);
|
||||
|
||||
change = changeLogListener.changes.getChanges().get(0);
|
||||
change = firstChange();
|
||||
|
||||
assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE);
|
||||
assertThat(change.getData()).isNull();
|
||||
|
||||
}
|
||||
|
||||
private Database createServer() {
|
||||
@@ -153,16 +164,15 @@ public class TestChangeLog extends BaseTestCase {
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
ChangeSet changes;
|
||||
List<ChangeSet> changes = new ArrayList<>();
|
||||
|
||||
public TDChangeLogListener() {
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void log(ChangeSet changes) {
|
||||
this.changes = changes;
|
||||
this.changes.add(changes);
|
||||
try {
|
||||
String json = objectMapper.writeValueAsString(changes);
|
||||
ChangeSet changes1 = objectMapper.readValue(json, ChangeSet.class);
|
||||
|
||||
@@ -24,7 +24,7 @@ public class TestInsertDuplicateKey extends BaseTestCase {
|
||||
public void clearDb() {
|
||||
server().find(Document.class).asDraft().where().contains("title", "UniqueKey").delete();
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void insert_duplicateKey() {
|
||||
|
||||
@@ -89,7 +89,7 @@ public class TestInsertDuplicateKey extends BaseTestCase {
|
||||
doc2.save();
|
||||
|
||||
// flush at this point, fails
|
||||
Ebean.getDefaultServer().currentTransaction().flushBatch();
|
||||
Ebean.getDefaultServer().currentTransaction().flush();
|
||||
} catch (DuplicateKeyException e) {
|
||||
log.info("duplicate failed but just continue" + e.getMessage());
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.tests.json;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.annotation.ForPlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.text.TextException;
|
||||
@@ -25,7 +25,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestDbJson_List extends BaseTestCase {
|
||||
|
||||
private EBasicJsonList bean = new EBasicJsonList();
|
||||
private final EBasicJsonList bean = new EBasicJsonList();
|
||||
|
||||
private EBasicJsonList found;
|
||||
|
||||
@@ -57,9 +57,9 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
bean.getBeanMap().put("key0", new PlainBean("k0", 90));
|
||||
bean.getBeanMap().put("key1", new PlainBean("k1", 91));
|
||||
|
||||
Ebean.save(bean);
|
||||
DB.save(bean);
|
||||
|
||||
found = Ebean.find(EBasicJsonList.class, bean.getId());
|
||||
found = DB.find(EBasicJsonList.class, bean.getId());
|
||||
|
||||
assertThat(found.getTags()).containsExactly("one", "two");
|
||||
assertTrue(found.getFlags().contains(42L));
|
||||
@@ -81,7 +81,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
//@Test//(dependsOnMethods = "insert")
|
||||
public void json_parse_format() {
|
||||
|
||||
String asJson = Ebean.json().toJson(found);
|
||||
String asJson = DB.json().toJson(found);
|
||||
assertThat(asJson).contains("\"tags\":[\"one\",\"two\"]");
|
||||
assertThat(asJson).contains("\"flags\":[42,43,44]");
|
||||
assertThat(asJson).contains("\"plainBean\":{\"name\":\"plain\"");
|
||||
@@ -90,7 +90,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
assertThat(asJson).contains("\"beanMap\":{");
|
||||
assertThat(asJson).contains("\"id\":");
|
||||
|
||||
EBasicJsonList fromJson = Ebean.json().toBean(EBasicJsonList.class, asJson);
|
||||
EBasicJsonList fromJson = DB.json().toBean(EBasicJsonList.class, asJson);
|
||||
assertEquals(found.getId(), fromJson.getId());
|
||||
assertEquals(found.getId(), fromJson.getId());
|
||||
assertEquals(found.getName(), fromJson.getName());
|
||||
@@ -109,7 +109,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
|
||||
found.setName("mod");
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.save(found);
|
||||
DB.save(found);
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
// we don't update the phone numbers (as they are not dirty)
|
||||
@@ -122,7 +122,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
found.getTags().add("three");
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.save(found);
|
||||
DB.save(found);
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
// we don't update the phone numbers (as they are not dirty)
|
||||
@@ -135,7 +135,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
found.getFlags().remove(42L);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.save(found);
|
||||
DB.save(found);
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
// we don't update the phone numbers (as they are not dirty)
|
||||
@@ -150,7 +150,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
found.getBeanMap().remove("key0");
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.save(found);
|
||||
DB.save(found);
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
// we don't update the phone numbers (as they are not dirty)
|
||||
@@ -166,13 +166,13 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
bean.setTags(null);
|
||||
bean.setBeanMap(null);
|
||||
|
||||
Ebean.save(bean);
|
||||
DB.save(bean);
|
||||
|
||||
EBasicJsonList found = Ebean.find(EBasicJsonList.class, bean.getId());
|
||||
EBasicJsonList found = DB.find(EBasicJsonList.class, bean.getId());
|
||||
|
||||
assertNull(found.getPlainBean());
|
||||
|
||||
String asJson = Ebean.json().toJson(found);
|
||||
String asJson = DB.json().toJson(found);
|
||||
assertNotNull(asJson);
|
||||
}
|
||||
|
||||
@@ -186,17 +186,16 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
plainBean.setName("Blubb");
|
||||
bean.getBeanMap().put("bla", plainBean);
|
||||
|
||||
Ebean.save(bean);
|
||||
DB.save(bean);
|
||||
|
||||
// set some invalid JSON content into DB
|
||||
Ebean.update(EBasicJsonList.class)
|
||||
.set("beanMap", "blabla")
|
||||
.where().eq("id", bean.getId())
|
||||
.update();
|
||||
DB.sqlUpdate("update ebasic_json_list set bean_map=? where id=?")
|
||||
.setParameters("blabla", bean.getId())
|
||||
.execute();
|
||||
|
||||
try {
|
||||
// a normal query fails due to invalid JSON content
|
||||
Ebean.find(EBasicJsonList.class)
|
||||
DB.find(EBasicJsonList.class)
|
||||
.setId(bean.getId())
|
||||
.findOne();
|
||||
|
||||
@@ -208,7 +207,7 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
assertThat(e.getMessage()).contains("beanMap");
|
||||
}
|
||||
|
||||
bean = Ebean.find(EBasicJsonList.class)
|
||||
bean = DB.find(EBasicJsonList.class)
|
||||
.setId(bean.getId())
|
||||
.setAllowLoadErrors() // allow invalid JSON content
|
||||
.findOne();
|
||||
@@ -220,6 +219,6 @@ public class TestDbJson_List extends BaseTestCase {
|
||||
.isInstanceOf(TextException.class)
|
||||
.hasMessageContaining("blabla");
|
||||
|
||||
Ebean.delete(bean);
|
||||
DB.delete(bean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package org.tests.json;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.text.json.EJson;
|
||||
import io.ebeantest.LoggedSql;
|
||||
import org.tests.model.json.EBasicJsonMap;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.json.EBasicJsonMapDetail;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -29,9 +31,9 @@ public class TestJsonMapBasic extends BaseTestCase {
|
||||
bean.getDetails().add(new EBasicJsonMapDetail("detail1"));
|
||||
bean.getDetails().add(new EBasicJsonMapDetail("detail2"));
|
||||
|
||||
Ebean.save(bean);
|
||||
DB.save(bean);
|
||||
|
||||
Query<EBasicJsonMap> query1 = Ebean.find(EBasicJsonMap.class)
|
||||
Query<EBasicJsonMap> query1 = DB.find(EBasicJsonMap.class)
|
||||
.fetch("details")
|
||||
.where().startsWith("details.name", "detail")
|
||||
.query();
|
||||
@@ -40,7 +42,7 @@ public class TestJsonMapBasic extends BaseTestCase {
|
||||
|
||||
assertThat(query1.getGeneratedSql()).contains("select distinct on (t0.id, t1.id) ");
|
||||
|
||||
Query<EBasicJsonMap> query2 = Ebean.find(EBasicJsonMap.class)
|
||||
Query<EBasicJsonMap> query2 = DB.find(EBasicJsonMap.class)
|
||||
.where().startsWith("details.name", "detail")
|
||||
.query();
|
||||
query2.findList();
|
||||
@@ -60,9 +62,9 @@ public class TestJsonMapBasic extends BaseTestCase {
|
||||
bean.setName("one");
|
||||
bean.setContent(content);
|
||||
|
||||
Ebean.save(bean);
|
||||
DB.save(bean);
|
||||
|
||||
EBasicJsonMap bean1 = Ebean.find(EBasicJsonMap.class, bean.getId());
|
||||
EBasicJsonMap bean1 = DB.find(EBasicJsonMap.class, bean.getId());
|
||||
|
||||
assertEquals(bean.getId(), bean1.getId());
|
||||
assertEquals(bean.getName(), bean1.getName());
|
||||
@@ -70,16 +72,16 @@ public class TestJsonMapBasic extends BaseTestCase {
|
||||
assertEquals(18L, bean1.getContent().get("docId"));
|
||||
|
||||
bean1.setName("just change name");
|
||||
Ebean.save(bean1);
|
||||
DB.save(bean1);
|
||||
|
||||
// content changes detected - dirty state so included in update
|
||||
Map<String, Object> content1 = bean1.getContent();
|
||||
content1.put("additional", "newValue");
|
||||
content1.put("docId", 99L);
|
||||
bean1.setName("two");
|
||||
Ebean.save(bean1);
|
||||
DB.save(bean1);
|
||||
|
||||
EBasicJsonMap bean2 = Ebean.find(EBasicJsonMap.class, bean.getId());
|
||||
EBasicJsonMap bean2 = DB.find(EBasicJsonMap.class, bean.getId());
|
||||
|
||||
// name changed and docId changed
|
||||
assertEquals("two", bean2.getName());
|
||||
@@ -89,12 +91,48 @@ public class TestJsonMapBasic extends BaseTestCase {
|
||||
content1.put("additional", "modValue");
|
||||
bean1.setName("three");
|
||||
bean1.setContent(content1);
|
||||
Ebean.save(bean1);
|
||||
DB.save(bean1);
|
||||
|
||||
EBasicJsonMap bean3 = Ebean.find(EBasicJsonMap.class, bean.getId());
|
||||
EBasicJsonMap bean3 = DB.find(EBasicJsonMap.class, bean.getId());
|
||||
|
||||
assertEquals("three", bean3.getName());
|
||||
assertEquals(99L, bean3.getContent().get("docId"));
|
||||
assertEquals("modValue", bean3.getContent().get("additional"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateQuery_bindingMap() throws IOException {
|
||||
|
||||
String s0 = "{\"docId\":22,\"contentId\":\"initialDoc\"}";
|
||||
Map<String, Object> content = EJson.parseObject(s0);
|
||||
|
||||
EBasicJsonMap bean = new EBasicJsonMap();
|
||||
bean.setName("one");
|
||||
bean.setContent(content);
|
||||
|
||||
DB.save(bean);
|
||||
|
||||
String s1 = "{\"docId\":222,\"contentId\":\"updatedDoc222\"}";
|
||||
Map<String, Object> content1 = EJson.parseObject(s1);
|
||||
|
||||
LoggedSql.start();
|
||||
|
||||
final int rows = DB.update(EBasicJsonMap.class)
|
||||
.set("content", content1)
|
||||
.where().eq("id", bean.getId())
|
||||
.update();
|
||||
|
||||
final List<String> sql = LoggedSql.stop();
|
||||
|
||||
assertThat(sql).hasSize(1);
|
||||
assertThat(sql.get(0)).contains("update ebasic_json_map set content=? where id = ?");
|
||||
assertThat(rows).isEqualTo(1);
|
||||
|
||||
final EBasicJsonMap found = DB.find(EBasicJsonMap.class, bean.getId());
|
||||
final Map<String, Object> content2 = found.getContent();
|
||||
assertThat(content2.get("contentId")).isEqualTo("updatedDoc222");
|
||||
assertThat(content2.get("docId")).isEqualTo(222L);
|
||||
|
||||
DB.delete(found);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.tests.lifecycle;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.EBasicLog;
|
||||
import org.tests.model.basic.EBasicWithLog;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
|
||||
public class TestLifecycleWithLog extends BaseTestCase {
|
||||
|
||||
private List<String> getLogs() {
|
||||
List<String> ret = DB.find(EBasicLog.class)
|
||||
.select("name")
|
||||
.findSingleAttributeList();
|
||||
DB.find(EBasicLog.class).delete();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCUD() {
|
||||
|
||||
EBasicWithLog bean = new EBasicWithLog();
|
||||
bean.setId(1L);
|
||||
bean.setName("Test1");
|
||||
|
||||
DB.save(bean);
|
||||
assertThat(getLogs()).contains("prePersist", "postPersist");
|
||||
|
||||
bean.setName("Test2");
|
||||
DB.save(bean);
|
||||
assertThat(getLogs()).contains("preUpdate", "postUpdate");
|
||||
|
||||
DB.delete(bean);
|
||||
assertThat(getLogs()).contains("preSoftDelete", "postSoftDelete");
|
||||
|
||||
DB.deletePermanent(bean);
|
||||
assertThat(getLogs()).contains("preRemove", "postRemove");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCUDBatch() {
|
||||
|
||||
EBasicWithLog bean = new EBasicWithLog();
|
||||
bean.setId(2L);
|
||||
bean.setName("Test2");
|
||||
|
||||
List<EBasicWithLog> beans = Arrays.asList(bean);
|
||||
|
||||
DB.saveAll(beans);
|
||||
assertThat(getLogs()).contains("prePersist", "postPersist");
|
||||
|
||||
bean.setName("Test2Modified");
|
||||
DB.saveAll(beans);
|
||||
assertThat(getLogs()).contains("preUpdate", "postUpdate");
|
||||
|
||||
DB.deleteAll(beans);
|
||||
assertThat(getLogs()).contains("preSoftDelete", "postSoftDelete");
|
||||
|
||||
DB.deleteAllPermanent(beans);
|
||||
assertThat(getLogs()).contains("preRemove", "postRemove");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.tests.model.basic;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "e_basic_log")
|
||||
public class EBasicLog {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.tests.model.basic;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.annotation.PostSoftDelete;
|
||||
import io.ebean.annotation.PreSoftDelete;
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.PostPersist;
|
||||
import javax.persistence.PostRemove;
|
||||
import javax.persistence.PostUpdate;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreRemove;
|
||||
import javax.persistence.PreUpdate;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
|
||||
@Entity
|
||||
@Table(name = "e_basic_withlog")
|
||||
public class EBasicWithLog {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
|
||||
@SoftDelete
|
||||
boolean deleted;
|
||||
|
||||
@Version
|
||||
Long version;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
writeLog("prePersist");
|
||||
}
|
||||
|
||||
@PostPersist
|
||||
public void postPersist() {
|
||||
writeLog("postPersist");
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
writeLog("preUpdate");
|
||||
}
|
||||
|
||||
@PostUpdate
|
||||
public void postUpdate() {
|
||||
writeLog("postUpdate");
|
||||
}
|
||||
|
||||
@PreRemove
|
||||
public void preRemove() {
|
||||
writeLog("preRemove");
|
||||
}
|
||||
|
||||
@PostRemove
|
||||
public void postRemove() {
|
||||
writeLog("postRemove");
|
||||
}
|
||||
|
||||
@PostSoftDelete
|
||||
public void postSoftDelete() {
|
||||
writeLog("postSoftDelete");
|
||||
}
|
||||
|
||||
@PreSoftDelete
|
||||
public void preSoftDelete() {
|
||||
writeLog("preSoftDelete");
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
public void postLoad() {
|
||||
writeLog("postLoad");
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
writeLog("postConstruct");
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
private void writeLog(String title) {
|
||||
EBasicLog log = new EBasicLog();
|
||||
log.setName(title);
|
||||
DB.save(log);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -40,7 +40,7 @@ public class TestInsertBatchThenFlushThenUpdate extends BaseTestCase {
|
||||
// nothing flushed yet
|
||||
assertThat(LoggedSqlCollector.start()).isEmpty();
|
||||
|
||||
txn.flushBatch();
|
||||
txn.flush();
|
||||
|
||||
List<String> loggedSql1 = LoggedSqlCollector.start();
|
||||
assertThat(loggedSql1).hasSize(4);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.tests.model.cache;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
@Entity
|
||||
@Cache(enableQueryCache = true, enableBeanCache = true)
|
||||
public class M2MCacheChild {
|
||||
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.tests.model.cache;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
@Entity
|
||||
@Cache(enableQueryCache = true, enableBeanCache = true)
|
||||
public class M2MCacheMaster {
|
||||
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
@JoinTable(name = "m2mcache_set1")
|
||||
private Set<M2MCacheChild> set1 = new LinkedHashSet<>();
|
||||
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
@JoinTable(name = "m2mcache_set2")
|
||||
private Set<M2MCacheChild> set2 = new LinkedHashSet<>();
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Set<M2MCacheChild> getSet1() {
|
||||
return set1;
|
||||
}
|
||||
|
||||
public void setSet1(Set<M2MCacheChild> set1) {
|
||||
this.set1 = set1;
|
||||
}
|
||||
|
||||
public Set<M2MCacheChild> getSet2() {
|
||||
return set2;
|
||||
}
|
||||
|
||||
public void setSet2(Set<M2MCacheChild> set2) {
|
||||
this.set2 = set2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.tests.model.history;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.annotation.IgnorePlatform;
|
||||
import io.ebean.annotation.Platform;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
@@ -16,7 +16,7 @@ public class TestHistoryOneToMany extends BaseTestCase {
|
||||
|
||||
@IgnorePlatform(Platform.ORACLE)
|
||||
@Test
|
||||
public void test() {
|
||||
public void test() throws InterruptedException {
|
||||
|
||||
HiTOne one = new HiTOne("one");
|
||||
|
||||
@@ -37,11 +37,12 @@ public class TestHistoryOneToMany extends BaseTestCase {
|
||||
one.getTwos().add(_22);
|
||||
|
||||
|
||||
Ebean.save(one);
|
||||
DB.save(one);
|
||||
Thread.sleep(20);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
List<HiTOne> list = Ebean.find(HiTOne.class)
|
||||
List<HiTOne> list = DB.find(HiTOne.class)
|
||||
.fetch("twos")
|
||||
.fetch("twos.threes")
|
||||
.where().ilike("name", "on%")
|
||||
@@ -51,15 +52,14 @@ public class TestHistoryOneToMany extends BaseTestCase {
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(list).hasSize(1);
|
||||
assertThat(list.get(0).getTwos()).hasSize(2);
|
||||
assertThat(list.get(0).getTwos().get(0).getThrees()).hasSize(3);
|
||||
|
||||
if (isH2()) {
|
||||
assertThat(sql).hasSize(2);
|
||||
assertSql(sql.get(0)).contains("from hi_tone_with_history t0 where (t0.sys_period_start <= ? and (t0.sys_period_end is null or t0.sys_period_end > ?)) and lower(t0.name) like ? escape'' limit 10");
|
||||
assertSql(sql.get(1)).contains("from hi_ttwo_with_history t0 left join hi_tthree_with_history t1 on t1.hi_ttwo_id = t0.id and (t1.sys_period_start <= ? and (t1.sys_period_end is null or t1.sys_period_end > ?)) where (t0.sys_period_start <= ? and (t0.sys_period_end is null or t0.sys_period_end > ?)) and (t0.hi_tone_id) in (?)");
|
||||
}
|
||||
|
||||
assertThat(list).hasSize(1);
|
||||
assertThat(list.get(0).getTwos()).hasSize(2);
|
||||
assertThat(list.get(0).getTwos().get(0).getThrees()).hasSize(3);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.tests.model.lazywithcache;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.Id;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
|
||||
/**
|
||||
* Class with @Cache and lazy load property.
|
||||
*
|
||||
* @author Noemi Szemenyei, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Cache(enableQueryCache = true)
|
||||
public class ChildWithCache {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
String name;
|
||||
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
String address;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.tests.model.lazywithcache;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* Parent class with ChildWithCache.
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
public class ParentA {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
ChildWithCache child;
|
||||
|
||||
String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ChildWithCache getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(ChildWithCache child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.tests.model.lazywithcache;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* Parent class with ChildWithCache.
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
public class ParentB {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
ChildWithCache child;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ChildWithCache getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
public void setChild(ChildWithCache child) {
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.tests.model.lazywithcache;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test with bean cache and lazy loaded property.
|
||||
*
|
||||
* @author Noemi Szemenyei, FOCONIS AG
|
||||
*
|
||||
*/
|
||||
public class TestWithCacheAndLazyLoad extends BaseTestCase{
|
||||
|
||||
@Test
|
||||
public void testGetters() {
|
||||
|
||||
ChildWithCache child = new ChildWithCache();
|
||||
child.setId(1L);
|
||||
child.setName("Child With Cache");
|
||||
child.setAddress("Address");
|
||||
DB.save(child);
|
||||
|
||||
ParentA parentA = new ParentA();
|
||||
parentA.setId(1L);
|
||||
parentA.setName("Parent A");
|
||||
parentA.setChild(child);
|
||||
DB.save(parentA);
|
||||
|
||||
ParentB parentB = new ParentB();
|
||||
parentB.setId(1L);
|
||||
parentB.setChild(child);
|
||||
DB.save(parentB);
|
||||
|
||||
|
||||
ParentA tempA = DB.find(ParentA.class, 1L);
|
||||
tempA.getChild().getName(); // load name
|
||||
|
||||
ParentB tempB = DB.find(ParentB.class, 1L);
|
||||
|
||||
ChildWithCache temp = tempB.getChild();
|
||||
// if the next line is commented out, the test passes
|
||||
temp.getName(); // load name from cache --> ebean_intercept.loadedFromCache = true
|
||||
|
||||
assertThat(temp.getAddress()).isEqualTo("Address");
|
||||
assertThat(temp.getName()).isEqualTo("Child With Cache");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,18 +3,24 @@ package org.tests.query.other;
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.CountDistinctOrder;
|
||||
import io.ebean.CountedValue;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.Query;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.tests.inherit.ChildA;
|
||||
import org.tests.inherit.Data;
|
||||
import org.tests.inherit.EUncle;
|
||||
import org.tests.lazyforeignkeys.MainEntity;
|
||||
import org.tests.lazyforeignkeys.MainEntityRelation;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
import org.tests.model.basic.VwCustomer;
|
||||
import org.tests.o2m.OmBasicParent;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.LocalDate;
|
||||
@@ -24,6 +30,46 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestQuerySingleAttribute extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesTwoToMany() {
|
||||
ResetBasicData.reset();
|
||||
// Query without ors with equals causing joins, only one Customer with name Rob exists
|
||||
Query<Customer> query0 = DB.find(Customer.class)
|
||||
.select("name")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where()
|
||||
.eq("name", "Rob")
|
||||
.query();
|
||||
|
||||
CountedValue<String> robs0 = (CountedValue<String>) query0.findSingleAttributeList().get(0);
|
||||
assertThat(robs0.getValue()).isEqualTo("Rob");
|
||||
assertThat(robs0.getCount()).isEqualTo(1);
|
||||
|
||||
// Query with or with equals causing joins
|
||||
Query<Customer> query = DB.find(Customer.class)
|
||||
.select("name")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where()
|
||||
.eq("name", "Rob")
|
||||
.or()
|
||||
.eq("orders.status", Order.Status.NEW)
|
||||
.eq("contacts.firstName", "Fred1")
|
||||
.endOr()
|
||||
.query();
|
||||
|
||||
CountedValue<String> robs = (CountedValue<String>) query.findSingleAttributeList().get(0);
|
||||
assertThat(robs.getValue()).isEqualTo("Rob");
|
||||
// only one Customer named rob exists, but 7 is returned for the amount of Customers named Rob
|
||||
assertThat(robs.getCount()).isEqualTo(1);
|
||||
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*) " +
|
||||
"from (select distinct t0.id, t0.name as attribute_ " +
|
||||
"from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id " +
|
||||
"where t0.name = ? and (u2.status = ? or u1.first_name = ?)) r1 " +
|
||||
"group by r1.attribute_ " +
|
||||
"order by count(*) desc, r1.attribute_");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exampleUsage() {
|
||||
|
||||
@@ -102,6 +148,93 @@ public class TestQuerySingleAttribute extends BaseTestCase {
|
||||
assertThat(name).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributeList_with_join_column() {
|
||||
ResetBasicData.reset();
|
||||
Query<MainEntityRelation> query = Ebean.find(MainEntityRelation.class)
|
||||
.fetch("entity1", "attr1")
|
||||
.setDistinct(true)
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
|
||||
List<CountedValue<String>> attr1list = query.findSingleAttributeList();
|
||||
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select distinct t0.id, t0.id1, t1.attr1 as attribute_ from main_entity_relation t0 left join main_entity t1 on t1.id = t0.id1) r1"
|
||||
+ " group by r1.attribute_"
|
||||
+ " order by count(*) desc, r1.attribute_"); // sub-query select clause includes t0.id1
|
||||
assertThat(attr1list).isNotNull();
|
||||
assertThat(attr1list).hasSize(2);
|
||||
assertThat(attr1list.get(0).getValue()).isEqualTo("a1");
|
||||
assertThat(attr1list.get(0).getCount()).isEqualTo(2l);
|
||||
assertThat(attr1list.get(1).getValue()).isEqualTo("a2");
|
||||
assertThat(attr1list.get(1).getCount()).isEqualTo(1l);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesVariousSelection1() {
|
||||
Query<MainEntityRelation> query = Ebean.find(MainEntityRelation.class)
|
||||
.fetch("entity1", "attr1")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
query.findSingleAttributeList();
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select t0.id1, t1.attr1 as attribute_ from main_entity_relation t0 left join main_entity t1 on t1.id = t0.id1) r1"
|
||||
+ " group by r1.attribute_"
|
||||
+ " order by count(*) desc, r1.attribute_"); // sub-query select clause includes t0.id1
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesVariousSelection2() {
|
||||
Query<MainEntityRelation> query = Ebean.find(MainEntityRelation.class)
|
||||
.select("attr1")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
query.findSingleAttributeList();
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select t0.attr1 as attribute_ from main_entity_relation t0) r1"
|
||||
+ " group by r1.attribute_"
|
||||
+ " order by count(*) desc, r1.attribute_");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesVariousSelection3() {
|
||||
Query<MainEntityRelation> query = Ebean.find(MainEntityRelation.class)
|
||||
.select("id")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
query.findSingleAttributeList();
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select t0.id as attribute_ from main_entity_relation t0) r1"
|
||||
+ " group by r1.attribute_"
|
||||
+ " order by count(*) desc, r1.attribute_");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesVariousSelection4() {
|
||||
Query<MainEntityRelation> query = Ebean.find(MainEntityRelation.class)
|
||||
.fetch("entity1", "id")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
query.findSingleAttributeList();
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select t0.id1, t1.id as attribute_ from main_entity_relation t0 left join main_entity t1 on t1.id = t0.id1) r1"
|
||||
+ " group by r1.attribute_"
|
||||
+ " order by count(*) desc, r1.attribute_"); // sub-query select clause includes t0.id1,
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttributesVariousSelection5() {
|
||||
Query<OmBasicParent> query = Ebean.find(OmBasicParent.class)
|
||||
.fetch("children", "name")
|
||||
.setCountDistinct(CountDistinctOrder.COUNT_DESC_ATTR_ASC)
|
||||
.where().query();
|
||||
query.findSingleAttributeList();
|
||||
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*)"
|
||||
+ " from (select t1.id, t1.name as attribute_ from om_basic_parent t0 left join om_basic_child t1 on t1.parent_id = t0.id) r1 "
|
||||
+ "group by r1.attribute_ order by count(*) desc, r1.attribute_"); // sub-query select clause includes t1.id,
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findSingleAttribute_with_aggregate() {
|
||||
|
||||
@@ -624,4 +757,43 @@ public class TestQuerySingleAttribute extends BaseTestCase {
|
||||
System.out.println(" count:" + entry.getCount()+" orderStatus:" + entry.getValue() );
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MainEntity e1 = new MainEntity();
|
||||
e1.setId("1");
|
||||
e1.setAttr1("a1");
|
||||
DB.save(e1);
|
||||
|
||||
MainEntity e2 = new MainEntity();
|
||||
e2.setId("2");
|
||||
e2.setAttr1("a2");
|
||||
DB.save(e2);
|
||||
|
||||
MainEntity e3 = new MainEntity();
|
||||
e3.setId("3");
|
||||
e3.setAttr1("a1");
|
||||
DB.save(e3);
|
||||
|
||||
MainEntityRelation rel = new MainEntityRelation();
|
||||
rel.setEntity1(e1);
|
||||
rel.setEntity2(e1);
|
||||
DB.save(rel);
|
||||
|
||||
rel = new MainEntityRelation();
|
||||
rel.setEntity1(e2);
|
||||
rel.setEntity2(e2);
|
||||
DB.save(rel);
|
||||
|
||||
rel = new MainEntityRelation();
|
||||
rel.setEntity1(e3);
|
||||
rel.setEntity2(e3);
|
||||
DB.save(rel);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
Ebean.find(MainEntityRelation.class).delete();
|
||||
Ebean.find(MainEntity.class).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,27 @@ public class TestSoftDeleteBasic extends BaseTestCase {
|
||||
query.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindChild_joinParent() {
|
||||
EBasicSoftDelete bean = new EBasicSoftDelete();
|
||||
bean.setName("softDelParent_withChild");
|
||||
bean.addChild("child1", 10);
|
||||
|
||||
DB.save(bean);
|
||||
|
||||
Query<EBasicSDChild> query = DB.find(EBasicSDChild.class)
|
||||
.where()
|
||||
.eq("owner.name", "softDelParent_withChild")
|
||||
.query();
|
||||
|
||||
List<EBasicSDChild> list = query.findList();
|
||||
assertSql(query).contains("join ebasic_soft_delete t1 on t1.id = t0.owner_id and t1.deleted =");
|
||||
assertThat(list).hasSize(1);
|
||||
|
||||
// Cleanup created entity
|
||||
DB.deletePermanent(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSoftDeletedList() {
|
||||
EBasicSoftDelete bean = new EBasicSoftDelete();
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.tests.model.basic.ESimple;
|
||||
/**
|
||||
* This test tests a strange bug in the 6.2.0. sqlserver JDBC driver.
|
||||
* (Version 6.1.7.jre8-preview works)
|
||||
*
|
||||
* <p>
|
||||
* https://github.com/Microsoft/mssql-jdbc/pull/374
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
@@ -29,7 +29,7 @@ public class TestSqlServerBatch extends BaseTestCase {
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ESimple model = new ESimple();
|
||||
model.setName("baz "+i);
|
||||
model.setName("baz " + i);
|
||||
Ebean.save(model);
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ public class TestSqlServerBatch extends BaseTestCase {
|
||||
txn.setGetGeneratedKeys(false);
|
||||
|
||||
// explicitly flush the JDBC batch buffer
|
||||
txn.flushBatch();
|
||||
txn.flush();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ESimple model = new ESimple();
|
||||
model.setName(i % 2 == 0 ? null:"foobar");
|
||||
Ebean.save(model);
|
||||
}
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ESimple model = new ESimple();
|
||||
model.setName(i % 2 == 0 ? null : "foobar");
|
||||
Ebean.save(model);
|
||||
}
|
||||
|
||||
// do not commit
|
||||
} finally {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean ddl generation</name>
|
||||
@@ -23,20 +23,20 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>12.2.0</version>
|
||||
<version>12.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>12.5.0</version>
|
||||
<version>12.6.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean external mapping api</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.6.2</tag>
|
||||
<tag>ebean-parent-12.6.6</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean external mapping xml</name>
|
||||
@@ -33,7 +33,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-externalmapping-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -59,14 +59,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean postgis</name>
|
||||
@@ -24,7 +24,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.6.2</tag>
|
||||
<tag>ebean-parent-12.6.6</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean querybean</name>
|
||||
@@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -67,14 +67,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<name>ebean redis</name>
|
||||
<description>Ebean Redis L2 Cache</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>3.4.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>12.6.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.composite</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.avaje.composite</groupId>
|
||||
<artifactId>logback</artifactId>
|
||||
<version>1.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>io.repaint.maven</groupId>
|
||||
<artifactId>tiles-maven-plugin</artifactId>
|
||||
<version>2.18</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<tiles>
|
||||
<tile>io.ebean.tile:enhancement:12.6.1</tile>
|
||||
</tiles>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCache;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class DuelCache implements ServerCache, NearCacheInvalidate {
|
||||
|
||||
private final DefaultServerCache near;
|
||||
private final RedisCache remote;
|
||||
private final NearCacheNotify cacheNotify;
|
||||
private final String cacheKey;
|
||||
|
||||
public DuelCache(DefaultServerCache near, RedisCache remote, String cacheKey, NearCacheNotify cacheNotify) {
|
||||
this.near = near;
|
||||
this.remote = remote;
|
||||
this.cacheKey = cacheKey;
|
||||
this.cacheNotify = cacheNotify;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
near.visit(visitor);
|
||||
remote.visit(visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateKeys(Set<Object> keySet) {
|
||||
near.removeAll(keySet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateKey(Object id) {
|
||||
near.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateClear() {
|
||||
near.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> getAll(Set<Object> keys) {
|
||||
|
||||
Map<Object, Object> resultMap = near.getAll(keys);
|
||||
Set<Object> localKeys = resultMap.keySet();
|
||||
|
||||
Set<Object> remainingKeys = new HashSet<>();
|
||||
for (Object key : keys) {
|
||||
if (!localKeys.contains(key)) {
|
||||
remainingKeys.add(key);
|
||||
}
|
||||
}
|
||||
if (!remainingKeys.isEmpty()) {
|
||||
// fetch missing ones from remote cache and merge results
|
||||
Map<Object, Object> remoteMap = remote.getAll(remainingKeys);
|
||||
if (!remoteMap.isEmpty()) {
|
||||
near.putAll(remoteMap);
|
||||
resultMap.putAll(remoteMap);
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object id) {
|
||||
Object val = near.get(id);
|
||||
if (val != null) {
|
||||
return val;
|
||||
}
|
||||
Object remoteVal = remote.get(id);
|
||||
if (remoteVal != null) {
|
||||
near.put(id, remoteVal);
|
||||
}
|
||||
return remoteVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<Object, Object> keyValues) {
|
||||
near.putAll(keyValues);
|
||||
remote.putAll(keyValues);
|
||||
cacheNotify.invalidateKeys(cacheKey, keyValues.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(Object id, Object value) {
|
||||
near.put(id, value);
|
||||
remote.put(id, value);
|
||||
cacheNotify.invalidateKey(cacheKey, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Set<Object> keys) {
|
||||
near.removeAll(keys);
|
||||
remote.removeAll(keys);
|
||||
cacheNotify.invalidateKeys(cacheKey, keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Object id) {
|
||||
near.remove(id);
|
||||
remote.remove(id);
|
||||
cacheNotify.invalidateKey(cacheKey, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
near.clear();
|
||||
remote.clear();
|
||||
cacheNotify.invalidateClear(cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the near cache hit count.
|
||||
*/
|
||||
public long getNearHitCount() {
|
||||
return near.getHitCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the near cache miss count.
|
||||
*/
|
||||
public long getNearMissCount() {
|
||||
return near.getMissCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the redis cache hit count.
|
||||
*/
|
||||
public long getRemoteHitCount() {
|
||||
return remote.getHitCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the redis cache miss count.
|
||||
*/
|
||||
public long getRemoteMissCount() {
|
||||
return remote.getMissCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHitRatio() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCacheStatistics getStatistics(boolean reset) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Provides a modified base64 encoded UUID and shorter 12 character random unique value.
|
||||
* <p>
|
||||
* <h3>newId()</h3>
|
||||
* <p>
|
||||
* It produces a 22 character string that is a base64 encoded UUID with the +
|
||||
* and / characters replaced with - and _ so as to be URL safe without requiring
|
||||
* encoding.
|
||||
* </p>
|
||||
* <h3>newShortId()</h3>
|
||||
* <p>
|
||||
* It produces a 12 character string that base64 encoded random number (72 bit).
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that this now internally uses java.util.Base64 to encode the values.
|
||||
* </p>
|
||||
*/
|
||||
public class ModId {
|
||||
|
||||
private static final SecureRandom shortIdSecureRandom = new SecureRandom();
|
||||
|
||||
private static final Base64.Encoder urlEncoder = Base64.getUrlEncoder();
|
||||
|
||||
/**
|
||||
* Return a 12 character string using a 72 bit randomly generated ID encoded
|
||||
* in modified base64.
|
||||
* <p>
|
||||
* A UUID is 128 bits and this is 72 bits so quite a bit smaller but still
|
||||
* very random with one in 4.7 * 10^21 chance of a collision.
|
||||
* </p>
|
||||
*/
|
||||
public static String id() {
|
||||
|
||||
// Random 72 bits
|
||||
byte[] randomBytes = new byte[9];
|
||||
shortIdSecureRandom.nextBytes(randomBytes);
|
||||
return encode64(randomBytes);
|
||||
}
|
||||
|
||||
private static String encode64(byte[] bytes) {
|
||||
return urlEncoder.encodeToString(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Near cache invalidation.
|
||||
*/
|
||||
public interface NearCacheInvalidate {
|
||||
|
||||
/**
|
||||
* Invalidate from near cache the given keys.
|
||||
*/
|
||||
void invalidateKeys(Set<Object> keySet);
|
||||
|
||||
/**
|
||||
* Invalidate from near cache the given key.
|
||||
*/
|
||||
void invalidateKey(Object id);
|
||||
|
||||
/**
|
||||
* Clear the near cache.
|
||||
*/
|
||||
void invalidateClear();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Notify other cluster members to invalidate parts of their near cache.
|
||||
*/
|
||||
public interface NearCacheNotify {
|
||||
|
||||
/**
|
||||
* Invalidate the given keys.
|
||||
*/
|
||||
void invalidateKeys(String cacheKey, Set<Object> keySet);
|
||||
|
||||
/**
|
||||
* Invalidate a single key.
|
||||
*/
|
||||
void invalidateKey(String cacheKey, Object id);
|
||||
|
||||
/**
|
||||
* Clear a near cache.
|
||||
*/
|
||||
void invalidateClear(String cacheKey);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.CountMetric;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.redis.encode.Encode;
|
||||
import io.ebean.redis.encode.EncodePrefixKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.ScanParams;
|
||||
import redis.clients.jedis.ScanResult;
|
||||
import redis.clients.jedis.util.SafeEncoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
class RedisCache implements ServerCache {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisCache.class);
|
||||
|
||||
private static final String CURSOR_0 = "0";
|
||||
private static final byte[] CURSOR_0_BYTES = SafeEncoder.encode(CURSOR_0);
|
||||
|
||||
private final JedisPool jedisPool;
|
||||
private final String cacheKey;
|
||||
private final Encode keyEncode;
|
||||
private final Encode valueEncode;
|
||||
|
||||
private final TimedMetric metricGet;
|
||||
private final TimedMetric metricGetAll;
|
||||
private final TimedMetric metricPut;
|
||||
private final TimedMetric metricPutAll;
|
||||
private final TimedMetric metricRemove;
|
||||
private final TimedMetric metricRemoveAll;
|
||||
private final TimedMetric metricClear;
|
||||
private final CountMetric hitCount;
|
||||
private final CountMetric missCount;
|
||||
|
||||
RedisCache(JedisPool jedisPool, ServerCacheConfig config, Encode valueEncode) {
|
||||
|
||||
this.jedisPool = jedisPool;
|
||||
this.cacheKey = config.getCacheKey();
|
||||
this.keyEncode = new EncodePrefixKey(config.getCacheKey());
|
||||
this.valueEncode = valueEncode;
|
||||
|
||||
String pre = "l2r.";
|
||||
String shortName = config.getShortName();
|
||||
MetricFactory factory = MetricFactory.get();
|
||||
|
||||
hitCount = factory.createCountMetric(pre + shortName + ".hit");
|
||||
missCount = factory.createCountMetric(pre + shortName + ".miss");
|
||||
metricGet = factory.createTimedMetric(pre + shortName + ".get");
|
||||
metricGetAll = factory.createTimedMetric(pre + shortName + ".getMany");
|
||||
metricPut = factory.createTimedMetric(pre + shortName + ".put");
|
||||
metricPutAll = factory.createTimedMetric(pre + shortName + ".putMany");
|
||||
metricRemove = factory.createTimedMetric(pre + shortName + ".remove");
|
||||
metricRemoveAll = factory.createTimedMetric(pre + shortName + ".removeMany");
|
||||
metricClear = factory.createTimedMetric(pre + shortName + ".clear");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
hitCount.visit(visitor);
|
||||
missCount.visit(visitor);
|
||||
metricGet.visit(visitor);
|
||||
metricGetAll.visit(visitor);
|
||||
metricPut.visit(visitor);
|
||||
metricPutAll.visit(visitor);
|
||||
metricRemove.visit(visitor);
|
||||
metricRemoveAll.visit(visitor);
|
||||
metricClear.visit(visitor);
|
||||
}
|
||||
|
||||
private byte[] key(Object id) {
|
||||
return keyEncode.encode(id);
|
||||
}
|
||||
|
||||
private byte[] value(Object data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return valueEncode.encode(data);
|
||||
}
|
||||
|
||||
private Object valueDecode(byte[] data) {
|
||||
try {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return valueEncode.decode(data);
|
||||
} catch (Exception e) {
|
||||
log.error("Error decoding data, treated as cache miss", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> getAll(Set<Object> keys) {
|
||||
|
||||
long start = System.nanoTime();
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
List<Object> keyList = new ArrayList<>(keys);
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
List<byte[]> valsAsBytes = resource.mget(keysAsBytes(keyList));
|
||||
for (int i = 0; i < keyList.size(); i++) {
|
||||
Object val = valueDecode(valsAsBytes.get(i));
|
||||
if (val != null) {
|
||||
map.put(keyList.get(i), val);
|
||||
}
|
||||
}
|
||||
int hits = map.size();
|
||||
int miss = keys.size() - hits;
|
||||
if (hits > 0) {
|
||||
hitCount.add(hits);
|
||||
}
|
||||
if (miss > 0) {
|
||||
missCount.add(miss);
|
||||
}
|
||||
metricGetAll.addSinceNanos(start);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(Object id) {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
Object val = valueDecode(resource.get(key(id)));
|
||||
if (val != null) {
|
||||
hitCount.increment();
|
||||
} else {
|
||||
missCount.increment();
|
||||
}
|
||||
metricGet.addSinceNanos(start);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void put(Object id, Object value) {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.set(key(id), value(value));
|
||||
metricPut.addSinceNanos(start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<Object, Object> keyValues) {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
byte[][] raw = new byte[keyValues.size() * 2][];
|
||||
int pos = 0;
|
||||
for (Map.Entry<Object, Object> entry : keyValues.entrySet()) {
|
||||
raw[pos++] = key(entry.getKey());
|
||||
raw[pos++] = value(entry.getValue());
|
||||
}
|
||||
resource.mset(raw);
|
||||
metricPutAll.addSinceNanos(start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Object id) {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.del(key(id));
|
||||
metricRemove.addSinceNanos(start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAll(Set<Object> keys) {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.del(keysAsBytes(keys));
|
||||
metricRemoveAll.addSinceNanos(start);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[][] keysAsBytes(Collection<Object> keys) {
|
||||
byte[][] raw = new byte[keys.size()][];
|
||||
int pos = 0;
|
||||
for (Object id : keys) {
|
||||
raw[pos++] = key(id);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
long start = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
|
||||
ScanParams params = new ScanParams();
|
||||
params.match(cacheKey + ":*");
|
||||
|
||||
String next;
|
||||
byte[] nextCursor = CURSOR_0_BYTES;
|
||||
do {
|
||||
ScanResult<byte[]> scanResult = resource.scan(nextCursor, params);
|
||||
List<byte[]> keys = scanResult.getResult();
|
||||
nextCursor = scanResult.getCursorAsBytes();
|
||||
|
||||
if (!keys.isEmpty()) {
|
||||
byte[][] raw = new byte[keys.size()][];
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
raw[i] = keys.get(i);
|
||||
}
|
||||
resource.del(raw);
|
||||
}
|
||||
|
||||
next = SafeEncoder.encode(nextCursor);
|
||||
} while (!next.equals("0"));
|
||||
|
||||
metricClear.addSinceNanos(start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of get hits.
|
||||
*/
|
||||
public long getHitCount() {
|
||||
return hitCount.get(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of get misses.
|
||||
*/
|
||||
public long getMissCount() {
|
||||
return missCount.get(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHitRatio() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCacheStatistics getStatistics(boolean reset) {
|
||||
|
||||
ServerCacheStatistics cacheStats = new ServerCacheStatistics();
|
||||
cacheStats.setCacheName(cacheKey);
|
||||
cacheStats.setHitCount(hitCount.get(reset));
|
||||
cacheStats.setMissCount(missCount.get(reset));
|
||||
cacheStats.setPutCount(metricPut.collect(reset).getCount());
|
||||
cacheStats.setRemoveCount(metricRemove.collect(reset).getCount());
|
||||
cacheStats.setClearCount(metricClear.collect(reset).getCount());
|
||||
return cacheStats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,525 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheNotification;
|
||||
import io.ebean.cache.ServerCacheNotify;
|
||||
import io.ebean.config.DatabaseConfig;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.redis.encode.EncodeBeanData;
|
||||
import io.ebean.redis.encode.EncodeManyIdsData;
|
||||
import io.ebean.redis.encode.EncodeSerializable;
|
||||
import io.ebean.redis.topic.DaemonTopic;
|
||||
import io.ebean.redis.topic.DaemonTopicRunner;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCache;
|
||||
import io.ebeaninternal.server.cache.DefaultServerCacheConfig;
|
||||
import io.ebeaninternal.server.cache.DefaultServerQueryCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.BinaryJedisPubSub;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.util.SafeEncoder;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
class RedisCacheFactory implements ServerCacheFactory {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisCacheFactory.class);
|
||||
|
||||
private static final Logger queryLogger = LoggerFactory.getLogger("io.ebean.cache.QUERY");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("io.ebean.cache.CACHE");
|
||||
|
||||
private static final Logger tableModLogger = LoggerFactory.getLogger("io.ebean.cache.TABLEMODS");
|
||||
|
||||
private static final int MSG_NEARCACHE_CLEAR = 1;
|
||||
private static final int MSG_NEARCACHE_KEYS = 2;
|
||||
private static final int MSG_NEARCACHE_KEY = 3;
|
||||
|
||||
/**
|
||||
* Channel for standard L2 cache messages.
|
||||
*/
|
||||
private static final String CHANNEL_L2 = "ebean.l2cache";
|
||||
|
||||
/**
|
||||
* Channel specifically for near cache invalidation messages.
|
||||
*/
|
||||
private static final String CHANNEL_NEAR = "ebean.l2near";
|
||||
|
||||
private static final byte[] CHANNEL_L2_BYTES = SafeEncoder.encode(CHANNEL_L2);
|
||||
private static final byte[] CHANNEL_NEAR_BYTES = SafeEncoder.encode(CHANNEL_NEAR);
|
||||
|
||||
private final ConcurrentHashMap<String, RQueryCache> queryCaches = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, NearCacheInvalidate> nearCacheMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final EncodeManyIdsData encodeManyIdsData = new EncodeManyIdsData();
|
||||
private final EncodeBeanData encodeBeanData = new EncodeBeanData();
|
||||
private final EncodeSerializable encodeSerializable = new EncodeSerializable();
|
||||
|
||||
private final BackgroundExecutor executor;
|
||||
|
||||
private final JedisPool jedisPool;
|
||||
|
||||
private final DaemonTopicRunner daemonTopicRunner;
|
||||
|
||||
private final NearCacheNotify nearCacheNotify;
|
||||
|
||||
private final TimedMetric metricOutNearCache;
|
||||
private final TimedMetric metricOutTableMod;
|
||||
private final TimedMetric metricOutQueryCache;
|
||||
private final TimedMetric metricInNearCache;
|
||||
private final TimedMetric metricInTableMod;
|
||||
private final TimedMetric metricInQueryCache;
|
||||
|
||||
private final String serverId = ModId.id();
|
||||
|
||||
private ServerCacheNotify listener;
|
||||
|
||||
RedisCacheFactory(DatabaseConfig config, BackgroundExecutor executor) {
|
||||
this.executor = executor;
|
||||
this.nearCacheNotify = new DNearCacheNotify();
|
||||
MetricFactory factory = MetricFactory.get();
|
||||
this.metricOutTableMod = factory.createTimedMetric("l2a.outTableMod");
|
||||
this.metricOutQueryCache = factory.createTimedMetric("l2a.outQueryCache");
|
||||
this.metricOutNearCache = factory.createTimedMetric("l2a.outNearKeys");
|
||||
this.metricInTableMod = factory.createTimedMetric("l2a.inTableMod");
|
||||
this.metricInQueryCache = factory.createTimedMetric("l2a.inQueryCache");
|
||||
this.metricInNearCache = factory.createTimedMetric("l2a.inNearKeys");
|
||||
if (config.isDisableL2Cache()) {
|
||||
this.jedisPool = null;
|
||||
this.daemonTopicRunner = null;
|
||||
} else {
|
||||
this.jedisPool = getJedisPool(config);
|
||||
this.daemonTopicRunner = new DaemonTopicRunner(jedisPool, new CacheDaemonTopic());
|
||||
daemonTopicRunner.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JedisPool to use (only 1 at this stage).
|
||||
*/
|
||||
private JedisPool getJedisPool(DatabaseConfig config) {
|
||||
JedisPool jedisPool = config.getServiceObject(JedisPool.class);
|
||||
if (jedisPool != null) {
|
||||
return jedisPool;
|
||||
}
|
||||
RedisConfig redisConfig = config.getServiceObject(RedisConfig.class);
|
||||
if (redisConfig == null) {
|
||||
redisConfig = new RedisConfig();
|
||||
}
|
||||
redisConfig.loadProperties(config.getProperties());
|
||||
log.info("using l2cache redis host {}:{}", redisConfig.getServer(), redisConfig.getPort());
|
||||
return redisConfig.createPool();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
metricOutQueryCache.visit(visitor);
|
||||
metricOutTableMod.visit(visitor);
|
||||
metricOutNearCache.visit(visitor);
|
||||
metricInTableMod.visit(visitor);
|
||||
metricInQueryCache.visit(visitor);
|
||||
metricInNearCache.visit(visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache createCache(ServerCacheConfig config) {
|
||||
if (config.isQueryCache()) {
|
||||
return createQueryCache(config);
|
||||
}
|
||||
return createNormalCache(config);
|
||||
}
|
||||
|
||||
private ServerCache createNormalCache(ServerCacheConfig config) {
|
||||
|
||||
RedisCache redisCache = createRedisCache(config);
|
||||
boolean nearCache = config.getCacheOptions().isNearCache();
|
||||
if (!nearCache) {
|
||||
return redisCache;
|
||||
}
|
||||
|
||||
String cacheKey = config.getCacheKey();
|
||||
|
||||
DefaultServerCache near = new DefaultServerCache(new DefaultServerCacheConfig(config));
|
||||
near.periodicTrim(executor);
|
||||
DuelCache duelCache = new DuelCache(near, redisCache, cacheKey, nearCacheNotify);
|
||||
nearCacheMap.put(cacheKey, duelCache);
|
||||
|
||||
return duelCache;
|
||||
}
|
||||
|
||||
private RedisCache createRedisCache(ServerCacheConfig config) {
|
||||
|
||||
switch (config.getType()) {
|
||||
case NATURAL_KEY:
|
||||
return new RedisCache(jedisPool, config, encodeSerializable);
|
||||
case BEAN:
|
||||
return new RedisCache(jedisPool, config, encodeBeanData);
|
||||
case COLLECTION_IDS:
|
||||
return new RedisCache(jedisPool, config, encodeManyIdsData);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected cache type? " + config.getType());
|
||||
}
|
||||
}
|
||||
|
||||
private ServerCache createQueryCache(ServerCacheConfig config) {
|
||||
synchronized (this) {
|
||||
RQueryCache cache = queryCaches.get(config.getCacheKey());
|
||||
if (cache == null) {
|
||||
logger.debug("create query cache [{}]", config.getCacheKey());
|
||||
cache = new RQueryCache(new DefaultServerCacheConfig(config));
|
||||
cache.periodicTrim(executor);
|
||||
queryCaches.put(config.getCacheKey(), cache);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCacheNotify createCacheNotify(ServerCacheNotify listener) {
|
||||
this.listener = listener;
|
||||
return new RServerCacheNotify();
|
||||
}
|
||||
|
||||
private void sendQueryCacheInvalidation(String name) {
|
||||
long nanos = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.publish(CHANNEL_L2, serverId + ":queryCache:" + name);
|
||||
} finally {
|
||||
metricOutQueryCache.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendTableMod(String formattedMsg) {
|
||||
long nanos = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.publish(CHANNEL_L2, serverId + ":tableMod:" + formattedMsg);
|
||||
} finally {
|
||||
metricOutTableMod.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query cache implementation using Redis channel for message notifications.
|
||||
*/
|
||||
private class RQueryCache extends DefaultServerQueryCache {
|
||||
|
||||
RQueryCache(DefaultServerCacheConfig config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
sendQueryCacheInvalidation(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the invalidation message coming from the cluster.
|
||||
*/
|
||||
private void invalidate() {
|
||||
queryLogger.debug(" CLEAR {}(*) - cluster invalidate", name);
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish table modifications using Redis channel (to other cluster members)
|
||||
*/
|
||||
private class RServerCacheNotify implements ServerCacheNotify {
|
||||
|
||||
@Override
|
||||
public void notify(ServerCacheNotification tableModifications) {
|
||||
|
||||
Set<String> dependentTables = tableModifications.getDependentTables();
|
||||
if (dependentTables != null && !dependentTables.isEmpty()) {
|
||||
|
||||
StringBuilder msg = new StringBuilder(50);
|
||||
for (String table : dependentTables) {
|
||||
msg.append(table).append(",");
|
||||
}
|
||||
|
||||
String formattedMsg = msg.toString();
|
||||
if (tableModLogger.isDebugEnabled()) {
|
||||
tableModLogger.debug("Publish TableMods - {}", formattedMsg);
|
||||
}
|
||||
sendTableMod(formattedMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the query cache if we have it.
|
||||
*/
|
||||
private void queryCacheInvalidate(String key) {
|
||||
long nanos = System.nanoTime();
|
||||
try {
|
||||
RQueryCache queryCache = queryCaches.get(key);
|
||||
if (queryCache != null) {
|
||||
queryCache.invalidate();
|
||||
}
|
||||
} finally {
|
||||
metricInQueryCache.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a remote dependent table modify event.
|
||||
*/
|
||||
private void processTableNotify(String rawMessage) {
|
||||
long nanos = System.nanoTime();
|
||||
try {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("processTableNotify {}", rawMessage);
|
||||
}
|
||||
Set<String> tables = new HashSet<>(asList(rawMessage.split(",")));
|
||||
listener.notify(new ServerCacheNotification(tables));
|
||||
} finally {
|
||||
metricInTableMod.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Near cache notification using a specific Redis channel (CHANNEL_NEAR).
|
||||
*/
|
||||
private class DNearCacheNotify implements NearCacheNotify {
|
||||
|
||||
@Override
|
||||
public void invalidateKeys(String cacheKey, Set<Object> keySet) {
|
||||
try {
|
||||
sendMessage(messageInvalidateKeys(cacheKey, keySet));
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to transmit invalidateKeys() message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateKey(String cacheKey, Object id) {
|
||||
try {
|
||||
sendMessage(messageInvalidateKey(cacheKey, id));
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to transmit invalidateKeys() message", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateClear(String cacheKey) {
|
||||
try {
|
||||
sendMessage(messageInvalidateClear(cacheKey));
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to transmit invalidateKeys() message", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(byte[] message) {
|
||||
long nanos = System.nanoTime();
|
||||
try (Jedis resource = jedisPool.getResource()) {
|
||||
resource.publish(CHANNEL_NEAR_BYTES, message);
|
||||
} finally {
|
||||
metricOutNearCache.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] messageInvalidateKeys(String cacheKey, Set<Object> keySet) throws IOException {
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream(100);
|
||||
ObjectOutputStream os = new ObjectOutputStream(ba);
|
||||
os.writeUTF(serverId);
|
||||
os.writeInt(MSG_NEARCACHE_KEYS);
|
||||
os.writeUTF(cacheKey);
|
||||
os.writeInt(keySet.size());
|
||||
for (Object key : keySet) {
|
||||
os.writeObject(key);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
return ba.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] messageInvalidateKey(String cacheKey, Object id) throws IOException {
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream(100);
|
||||
ObjectOutputStream os = new ObjectOutputStream(ba);
|
||||
os.writeUTF(serverId);
|
||||
os.writeInt(MSG_NEARCACHE_KEY);
|
||||
os.writeUTF(cacheKey);
|
||||
os.writeObject(id);
|
||||
os.flush();
|
||||
os.close();
|
||||
return ba.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] messageInvalidateClear(String cacheKey) throws IOException {
|
||||
ByteArrayOutputStream ba = new ByteArrayOutputStream(100);
|
||||
ObjectOutputStream os = new ObjectOutputStream(ba);
|
||||
os.writeUTF(serverId);
|
||||
os.writeInt(MSG_NEARCACHE_CLEAR);
|
||||
os.writeUTF(cacheKey);
|
||||
os.flush();
|
||||
os.close();
|
||||
return ba.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis channel listener that supports reconnection etc.
|
||||
*/
|
||||
private class CacheDaemonTopic implements DaemonTopic {
|
||||
|
||||
@Override
|
||||
public void subscribe(Jedis jedis) {
|
||||
jedis.subscribe(new ChannelSubscriber(), CHANNEL_L2_BYTES, CHANNEL_NEAR_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyConnected() {
|
||||
logger.info("Established connection to Redis");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updates to the features (via redis topic notifications).
|
||||
*/
|
||||
private class ChannelSubscriber extends BinaryJedisPubSub {
|
||||
|
||||
@Override
|
||||
public void onMessage(byte[] channel, byte[] message) {
|
||||
String channelName = SafeEncoder.encode(channel);
|
||||
if (channelName.equals(CHANNEL_L2)) {
|
||||
processL2Message(SafeEncoder.encode(message));
|
||||
} else {
|
||||
processNearCacheMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void processNearCacheMessage(byte[] message) {
|
||||
long nanos = System.nanoTime();
|
||||
int msgType = 0;
|
||||
String cacheKey = null;
|
||||
try {
|
||||
ObjectInputStream oi = new ObjectInputStream(new ByteArrayInputStream(message));
|
||||
String sourceServerId = oi.readUTF();
|
||||
if (sourceServerId.equals(serverId)) {
|
||||
// ignore this message as we are the server that sent it
|
||||
return;
|
||||
}
|
||||
msgType = oi.readInt();
|
||||
cacheKey = oi.readUTF();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("processNearCacheMessage serverId:{} type:{} cacheKey:{}", sourceServerId, msgType, cacheKey);
|
||||
}
|
||||
|
||||
switch (msgType) {
|
||||
case MSG_NEARCACHE_CLEAR:
|
||||
nearCacheInvalidateClear(cacheKey);
|
||||
break;
|
||||
|
||||
case MSG_NEARCACHE_KEY:
|
||||
Object key = oi.readObject();
|
||||
nearCacheInvalidateKey(cacheKey, key);
|
||||
break;
|
||||
|
||||
case MSG_NEARCACHE_KEYS:
|
||||
int count = oi.readInt();
|
||||
Set<Object> keys = new LinkedHashSet<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
keys.add(oi.readObject());
|
||||
}
|
||||
nearCacheInvalidateKeys(cacheKey, keys);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected message type ? " + msgType);
|
||||
}
|
||||
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
logger.error("failed to decode near cache message [" + SafeEncoder.encode(message) + "] for cache:" + cacheKey, e);
|
||||
if (cacheKey != null) {
|
||||
nearCacheInvalidateClear(cacheKey);
|
||||
}
|
||||
} finally {
|
||||
if (msgType != 0) {
|
||||
metricInNearCache.addSinceNanos(nanos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processL2Message(String message) {
|
||||
try {
|
||||
String[] split = message.split(":");
|
||||
if (serverId.equals(split[0])) {
|
||||
// ignore this message as we are the server that sent it
|
||||
return;
|
||||
}
|
||||
switch (split[1]) {
|
||||
case "tableMod":
|
||||
processTableNotify(split[2]);
|
||||
break;
|
||||
case "queryCache":
|
||||
queryCacheInvalidate(split[2]);
|
||||
break;
|
||||
default:
|
||||
logger.error("Unknown L2 message type[{}] on redis channel - message[{}] ", split[0], message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error handling L2 message[" + message + "]", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate key for a local near cache.
|
||||
*/
|
||||
private void nearCacheInvalidateKey(String cacheKey, Object key) {
|
||||
NearCacheInvalidate invalidate = nearCacheMap.get(cacheKey);
|
||||
if (invalidate == null) {
|
||||
warnNearCacheNotFound(cacheKey);
|
||||
} else {
|
||||
invalidate.invalidateKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate keys for a local near cache.
|
||||
*/
|
||||
private void nearCacheInvalidateKeys(String cacheKey, Set<Object> keys) {
|
||||
NearCacheInvalidate invalidate = nearCacheMap.get(cacheKey);
|
||||
if (invalidate == null) {
|
||||
warnNearCacheNotFound(cacheKey);
|
||||
} else {
|
||||
invalidate.invalidateKeys(keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate clear for a local near cache.
|
||||
*/
|
||||
private void nearCacheInvalidateClear(String cacheKey) {
|
||||
NearCacheInvalidate invalidate = nearCacheMap.get(cacheKey);
|
||||
if (invalidate == null) {
|
||||
warnNearCacheNotFound(cacheKey);
|
||||
} else {
|
||||
invalidate.invalidateClear();
|
||||
}
|
||||
}
|
||||
|
||||
private void warnNearCacheNotFound(String cacheKey) {
|
||||
logger.warn("No near cache found for cacheKey [" + cacheKey + "] yet - probably on startup");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.DatabaseConfig;
|
||||
|
||||
public class RedisCachePlugin implements ServerCachePlugin {
|
||||
|
||||
/**
|
||||
* Create the ServerCacheFactory implementation.
|
||||
*/
|
||||
@Override
|
||||
public ServerCacheFactory create(DatabaseConfig config, BackgroundExecutor executor) {
|
||||
return new RedisCacheFactory(config, executor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Deployment configuration for redis.
|
||||
*/
|
||||
public class RedisConfig {
|
||||
|
||||
private String server = "localhost";
|
||||
|
||||
private int port = 6379;
|
||||
|
||||
private int maxTotal = 200;
|
||||
|
||||
private int maxIdle = 200;
|
||||
|
||||
private int minIdle = 1;
|
||||
|
||||
private long maxWaitMillis = -1L;
|
||||
|
||||
private boolean blockWhenExhausted = true;
|
||||
|
||||
private int timeout = 2000;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private boolean ssl;
|
||||
|
||||
/**
|
||||
* Return a new JedisPool based on the configuration.
|
||||
*/
|
||||
public JedisPool createPool() {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
poolConfig.setMaxTotal(maxTotal);
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
poolConfig.setMaxWaitMillis(maxWaitMillis);
|
||||
poolConfig.setBlockWhenExhausted(blockWhenExhausted);
|
||||
return new JedisPool(poolConfig, server, port, timeout, username, password, ssl);
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public void setServer(String server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getMaxTotal() {
|
||||
return maxTotal;
|
||||
}
|
||||
|
||||
public void setMaxTotal(int maxTotal) {
|
||||
this.maxTotal = maxTotal;
|
||||
}
|
||||
|
||||
public int getMaxIdle() {
|
||||
return maxIdle;
|
||||
}
|
||||
|
||||
public void setMaxIdle(int maxIdle) {
|
||||
this.maxIdle = maxIdle;
|
||||
}
|
||||
|
||||
public int getMinIdle() {
|
||||
return minIdle;
|
||||
}
|
||||
|
||||
public void setMinIdle(int minIdle) {
|
||||
this.minIdle = minIdle;
|
||||
}
|
||||
|
||||
public long getMaxWaitMillis() {
|
||||
return maxWaitMillis;
|
||||
}
|
||||
|
||||
public void setMaxWaitMillis(long maxWaitMillis) {
|
||||
this.maxWaitMillis = maxWaitMillis;
|
||||
}
|
||||
|
||||
public boolean isBlockWhenExhausted() {
|
||||
return blockWhenExhausted;
|
||||
}
|
||||
|
||||
public void setBlockWhenExhausted(boolean blockWhenExhausted) {
|
||||
this.blockWhenExhausted = blockWhenExhausted;
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public void setTimeout(int timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public boolean isSsl() {
|
||||
return ssl;
|
||||
}
|
||||
|
||||
public void setSsl(boolean ssl) {
|
||||
this.ssl = ssl;
|
||||
}
|
||||
|
||||
public void loadProperties(Properties properties) {
|
||||
Reader reader = new Reader(properties);
|
||||
this.server = reader.get("ebean.redis.server", server);
|
||||
this.port = reader.getInt("ebean.redis.port", port);
|
||||
this.ssl = reader.getBool("ebean.redis.ssl", ssl);
|
||||
this.minIdle = reader.getInt("ebean.redis.minIdle", minIdle);
|
||||
this.maxIdle = reader.getInt("ebean.redis.maxIdle", maxIdle);
|
||||
this.maxTotal = reader.getInt("ebean.redis.maxTotal", maxTotal);
|
||||
this.maxWaitMillis = reader.getLong("ebean.redis.maxWaitMillis", maxWaitMillis);
|
||||
this.timeout = reader.getInt("ebean.redis.timeout", timeout);
|
||||
this.username = reader.get("ebean.redis.username", username);
|
||||
this.password = reader.get("ebean.redis.password", password);
|
||||
this.blockWhenExhausted = reader.getBool("ebean.redis.blockWhenExhausted", blockWhenExhausted);
|
||||
}
|
||||
|
||||
private static class Reader {
|
||||
|
||||
private final Properties properties;
|
||||
|
||||
Reader(Properties properties) {
|
||||
this.properties = (properties != null) ? properties : new Properties();
|
||||
}
|
||||
|
||||
String get(String key, String defaultVal) {
|
||||
return System.getProperty(key, properties.getProperty(key, defaultVal));
|
||||
}
|
||||
|
||||
int getInt(String key, int defaultVal) {
|
||||
final String val = get(key, null);
|
||||
return val != null ? Integer.parseInt(val.trim()) : defaultVal;
|
||||
}
|
||||
|
||||
long getLong(String key, long defaultVal) {
|
||||
final String val = get(key, null);
|
||||
return val != null ? Integer.parseInt(val.trim()) : defaultVal;
|
||||
}
|
||||
|
||||
boolean getBool(String key, boolean defaultVal) {
|
||||
final String val = get(key, null);
|
||||
return val != null ? Boolean.parseBoolean(val.trim()) : defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
public interface Encode {
|
||||
|
||||
byte[] encode(Object value);
|
||||
|
||||
Object decode(byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
public class EncodeBeanData implements Encode {
|
||||
|
||||
@Override
|
||||
public byte[] encode(Object value) {
|
||||
|
||||
try {
|
||||
CachedBeanData data = (CachedBeanData) value;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
|
||||
data.writeExternal(oos);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
|
||||
return os.toByteArray();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(byte[] data) {
|
||||
|
||||
try {
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(data);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
|
||||
CachedBeanData read = new CachedBeanData();
|
||||
read.readExternal(ois);
|
||||
return read;
|
||||
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
import io.ebeaninternal.server.cache.CachedManyIds;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
public class EncodeManyIdsData implements Encode {
|
||||
|
||||
@Override
|
||||
public byte[] encode(Object value) {
|
||||
|
||||
try {
|
||||
CachedManyIds data = (CachedManyIds) value;
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
|
||||
data.writeExternal(oos);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
|
||||
return os.toByteArray();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(byte[] data) {
|
||||
|
||||
try {
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(data);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
|
||||
CachedManyIds read = new CachedManyIds();
|
||||
read.readExternal(ois);
|
||||
return read;
|
||||
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
import io.ebean.cache.TenantAwareKey;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class EncodePrefixKey implements Encode {
|
||||
|
||||
private final String prefix;
|
||||
|
||||
public EncodePrefixKey(String cacheKey) {
|
||||
this.prefix = cacheKey + ":";
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(Object value) {
|
||||
try {
|
||||
if (!(value instanceof String) && !(value instanceof TenantAwareKey.CacheKey)) {
|
||||
throw new IllegalStateException("Expecting String keys but got type:" + value.getClass());
|
||||
}
|
||||
|
||||
String key = prefix + value.toString();
|
||||
return key.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(byte[] data) {
|
||||
try {
|
||||
String key = new String(data, StandardCharsets.UTF_8);
|
||||
int pos = key.indexOf(':');
|
||||
return key.substring(pos);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
public class EncodeSerializable implements Encode {
|
||||
|
||||
@Override
|
||||
public byte[] encode(Object value) {
|
||||
|
||||
try {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream());
|
||||
oos.writeObject(value);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
|
||||
return new ByteArrayOutputStream().toByteArray();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(byte[] data) {
|
||||
try {
|
||||
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data))) {
|
||||
return ois.readObject();
|
||||
}
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
throw new RuntimeException("Failed to decode cache data", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.redis.encode;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PrefixKey implements Serializable {
|
||||
|
||||
private final String prefix;
|
||||
private final Object key;
|
||||
|
||||
PrefixKey(String prefix, Object key) {
|
||||
this.prefix = prefix;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ebean.redis.topic;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
/**
|
||||
* Topic subscriber that has re-connect notification.
|
||||
*/
|
||||
public interface DaemonTopic {
|
||||
|
||||
/**
|
||||
* Subscribe to the topic/channel (blocking).
|
||||
*
|
||||
* @param jedis The redis connection to subscribe (and block on).
|
||||
*/
|
||||
void subscribe(Jedis jedis);
|
||||
|
||||
/**
|
||||
* Notify that the topic subscription has been connected (or reconnected).
|
||||
*/
|
||||
void notifyConnected();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package io.ebean.redis.topic;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* Subscribe to redis topic listening for changes.
|
||||
* <p>
|
||||
* Handles reconnection to redis when the connection is lost and
|
||||
* notification of when reconnection takes place.
|
||||
* </p>
|
||||
*/
|
||||
public class DaemonTopicRunner {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DaemonTopicRunner.class);
|
||||
|
||||
private static final long reconnectWaitMillis = 1000;
|
||||
|
||||
private final JedisPool jedisPool;
|
||||
|
||||
private final DaemonTopic daemonTopic;
|
||||
|
||||
public DaemonTopicRunner(JedisPool jedisPool, DaemonTopic daemonTopic) {
|
||||
this.jedisPool = jedisPool;
|
||||
this.daemonTopic = daemonTopic;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Thread t = new Thread(() -> attemptConnections(), "redis-sub");
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void attemptConnections() {
|
||||
|
||||
Timer reloadTimer = new Timer("redis-sub-notify");
|
||||
ReloadNotifyTask notifyTask = null;
|
||||
int attempts = 1;
|
||||
|
||||
while (true) {
|
||||
if (notifyTask != null) {
|
||||
// we didn't successfully re-connect to redis
|
||||
notifyTask.cancel();
|
||||
}
|
||||
notifyTask = new ReloadNotifyTask();
|
||||
reloadTimer.schedule(notifyTask, reconnectWaitMillis + 500);
|
||||
attempts++;
|
||||
try {
|
||||
subscribe();
|
||||
} catch (JedisException e) {
|
||||
log.debug("... redis subscribe connection attempt:{} failed:{}", attempts, e.getMessage());
|
||||
try {
|
||||
// wait a little before retrying
|
||||
Thread.sleep(reconnectWaitMillis);
|
||||
} catch (InterruptedException e1) {
|
||||
log.warn("Interrupted redis re-connection wait", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe and block when successful.
|
||||
*/
|
||||
private void subscribe() {
|
||||
Jedis jedis = jedisPool.getResource();
|
||||
jedis.echo("hi");
|
||||
try {
|
||||
daemonTopic.subscribe(jedis);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Lost connection to topic, starting re-connection loop", e);
|
||||
attemptConnections();
|
||||
|
||||
} finally {
|
||||
try {
|
||||
jedis.close();
|
||||
} catch (Exception e) {
|
||||
log.warn("Error closing probably broken Redis connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ReloadNotifyTask extends TimerTask {
|
||||
@Override
|
||||
public void run() {
|
||||
daemonTopic.notifyConnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.ebean.redis.RedisCachePlugin
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.ebean.redis;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class RedisConfigTest {
|
||||
|
||||
@Test
|
||||
public void loadProperties() {
|
||||
|
||||
Properties p = new Properties();
|
||||
p.setProperty("ebean.redis.server", "test-server");
|
||||
p.setProperty("ebean.redis.port", "99");
|
||||
p.setProperty("ebean.redis.maxIdle", "5");
|
||||
p.setProperty("ebean.redis.maxTotal", "6");
|
||||
p.setProperty("ebean.redis.minIdle", "7");
|
||||
p.setProperty("ebean.redis.maxWaitMillis", "8");
|
||||
p.setProperty("ebean.redis.username", "un");
|
||||
p.setProperty("ebean.redis.password", "pw");
|
||||
p.setProperty("ebean.redis.ssl", "true");
|
||||
|
||||
RedisConfig config = new RedisConfig();
|
||||
config.loadProperties(p);
|
||||
|
||||
assertThat(config.getServer()).isEqualTo("test-server");
|
||||
assertThat(config.getPort()).isEqualTo(99);
|
||||
assertThat(config.getMaxIdle()).isEqualTo(5);
|
||||
assertThat(config.getMaxTotal()).isEqualTo(6);
|
||||
assertThat(config.getMinIdle()).isEqualTo(7);
|
||||
assertThat(config.getMaxWaitMillis()).isEqualTo(8);
|
||||
assertThat(config.getUsername()).isEqualTo("un");
|
||||
assertThat(config.getPassword()).isEqualTo("pw");
|
||||
assertThat(config.isSsl()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_defaultValues() {
|
||||
RedisConfig config = new RedisConfig();
|
||||
assertThat(config.getUsername()).isNull();
|
||||
assertThat(config.getPassword()).isNull();
|
||||
assertThat(config.isSsl()).isEqualTo(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.domain;
|
||||
|
||||
import io.ebean.Model;
|
||||
import io.ebean.annotation.WhenCreated;
|
||||
import io.ebean.annotation.WhenModified;
|
||||
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Version;
|
||||
import java.time.Instant;
|
||||
|
||||
@MappedSuperclass
|
||||
public class EBase extends Model {
|
||||
|
||||
@Id
|
||||
protected long id;
|
||||
|
||||
@Version
|
||||
protected long version;
|
||||
|
||||
@WhenCreated
|
||||
protected Instant whenCreated;
|
||||
|
||||
@WhenModified
|
||||
protected Instant whenModified;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Instant getWhenCreated() {
|
||||
return whenCreated;
|
||||
}
|
||||
|
||||
public void setWhenCreated(Instant whenCreated) {
|
||||
this.whenCreated = whenCreated;
|
||||
}
|
||||
|
||||
public Instant getWhenModified() {
|
||||
return whenModified;
|
||||
}
|
||||
|
||||
public void setWhenModified(Instant whenModified) {
|
||||
this.whenModified = whenModified;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.domain;
|
||||
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
import io.ebean.annotation.Index;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Cache(enableQueryCache = true, nearCache = true, naturalKey = "name")
|
||||
@Entity
|
||||
public class Person extends EBase {
|
||||
|
||||
public enum Status {
|
||||
NEW,
|
||||
ACTIVE,
|
||||
INACTIVE
|
||||
}
|
||||
|
||||
@Index(unique = true)
|
||||
String name;
|
||||
|
||||
Status status;
|
||||
|
||||
LocalDate localDate;
|
||||
|
||||
String notes;
|
||||
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
this.status = Status.NEW;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[id:" + id + " name:" + name + "date:" + localDate + ']';
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getNotes() {
|
||||
return notes;
|
||||
}
|
||||
|
||||
public void setNotes(String notes) {
|
||||
this.notes = notes;
|
||||
}
|
||||
|
||||
public LocalDate getLocalDate() {
|
||||
return localDate;
|
||||
}
|
||||
|
||||
public void setLocalDate(LocalDate localDate) {
|
||||
this.localDate = localDate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.integration;
|
||||
|
||||
import io.ebean.redis.DuelCache;
|
||||
import org.domain.Person;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Database;
|
||||
import io.ebean.DatabaseFactory;
|
||||
import io.ebean.config.DatabaseConfig;
|
||||
import org.domain.query.QPerson;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ClusterTest {
|
||||
|
||||
|
||||
private Database createOther(DataSource dataSource) {
|
||||
|
||||
DatabaseConfig config = new DatabaseConfig();
|
||||
config.setDataSource(dataSource);
|
||||
config.loadFromProperties();
|
||||
config.setDefaultServer(false);
|
||||
config.setName("other");
|
||||
config.setDdlGenerate(false);
|
||||
config.setDdlRun(false);
|
||||
|
||||
return DatabaseFactory.create(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBothNear() {
|
||||
// ensure the default server exists first
|
||||
final Database db = DB.getDefault();
|
||||
Database other = createOther(db.getPluginApi().getDataSource());
|
||||
|
||||
new QPerson()
|
||||
.name.eq("Someone")
|
||||
.delete();
|
||||
|
||||
Person foo = new Person("Someone");
|
||||
foo.save();
|
||||
|
||||
DB.getServerCacheManager().clearAll();
|
||||
other.getMetaInfoManager().resetAllMetrics();
|
||||
|
||||
Person fooA = DB.find(Person.class, foo.getId());
|
||||
Person fooB = other.find(Person.class, foo.getId());
|
||||
fooA = DB.find(Person.class, foo.getId());
|
||||
fooB = other.find(Person.class, foo.getId());
|
||||
fooA = DB.find(Person.class, foo.getId());
|
||||
fooB = other.find(Person.class, foo.getId());
|
||||
|
||||
DuelCache dualCache = (DuelCache) other.getServerCacheManager().getBeanCache(Person.class);
|
||||
assertCounts(dualCache, 2, 1, 1, 0);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws InterruptedException {
|
||||
|
||||
// ensure the default server exists first
|
||||
final Database db = DB.getDefault();
|
||||
|
||||
Database other = createOther(db.getPluginApi().getDataSource());
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Person foo = new Person("name " + i);
|
||||
foo.save();
|
||||
}
|
||||
|
||||
other.getServerCacheManager().clearAll();
|
||||
other.getMetaInfoManager().resetAllMetrics();
|
||||
|
||||
DuelCache dualCache = (DuelCache) other.getServerCacheManager().getBeanCache(Person.class);
|
||||
|
||||
Person foo0 = other.find(Person.class, 1);
|
||||
assertCounts(dualCache, 0, 1, 0, 1);
|
||||
|
||||
other.find(Person.class, 1);
|
||||
assertCounts(dualCache, 1, 1, 0, 1);
|
||||
|
||||
other.find(Person.class, 1);
|
||||
assertCounts(dualCache, 2, 1, 0, 1);
|
||||
|
||||
other.find(Person.class, 1);
|
||||
assertCounts(dualCache, 3, 1, 0, 1);
|
||||
|
||||
other.find(Person.class, 2);
|
||||
assertCounts(dualCache, 3, 2, 0, 2);
|
||||
|
||||
|
||||
foo0.setName("name2");
|
||||
foo0.save();
|
||||
allowAsyncMessaging();
|
||||
|
||||
Person foo3 = other.find(Person.class, 1);
|
||||
assertThat(foo3.getName()).isEqualTo("name2");
|
||||
assertCounts(dualCache, 3, 3, 1, 2);
|
||||
|
||||
|
||||
foo0.setName("name3");
|
||||
foo0.save();
|
||||
allowAsyncMessaging();
|
||||
|
||||
foo3 = other.find(Person.class, 1);
|
||||
assertThat(foo3.getName()).isEqualTo("name3");
|
||||
assertCounts(dualCache, 3, 4, 2, 2);
|
||||
}
|
||||
|
||||
private void assertCounts(DuelCache dualCache, int nearHits, int nearMiss, int remoteHit, int remoteMiss) {
|
||||
|
||||
assertThat(dualCache.getNearHitCount()).isEqualTo(nearHits);
|
||||
assertThat(dualCache.getNearMissCount()).isEqualTo(nearMiss);
|
||||
assertThat(dualCache.getRemoteHitCount()).isEqualTo(remoteHit);
|
||||
assertThat(dualCache.getRemoteMissCount()).isEqualTo(remoteMiss);
|
||||
}
|
||||
|
||||
private void allowAsyncMessaging() throws InterruptedException {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.integration;
|
||||
|
||||
import io.ebean.DB;
|
||||
import org.domain.Person;
|
||||
import org.domain.query.QPerson;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() throws InterruptedException {
|
||||
|
||||
insertSomePeople();
|
||||
|
||||
Person fiona = findByName("Fiona");
|
||||
fiona.setName("Fortuna");
|
||||
fiona.setLocalDate(LocalDate.now());
|
||||
fiona.update();
|
||||
|
||||
Thread.sleep(100);
|
||||
|
||||
Person one = findById(1);
|
||||
assertThat(one).isNotNull();
|
||||
|
||||
for (int i = 1; i < 4; i++) {
|
||||
System.out.println("hit " + findById(i));
|
||||
}
|
||||
|
||||
List<Person> one2 = nameStartsWith("fo");
|
||||
assertThat(one2).hasSize(1);
|
||||
|
||||
one2 = nameStartsWith("j");
|
||||
assertThat(one2).hasSize(2);
|
||||
|
||||
one2 = nameStartsWith("j");
|
||||
assertThat(one2).hasSize(2);
|
||||
|
||||
|
||||
List<Person> byNames = findByNames("Jack", "Rob");
|
||||
assertThat(byNames).hasSize(2);
|
||||
|
||||
byNames = findByNames("Jack", "Rob", "Moby");
|
||||
assertThat(byNames).hasSize(3);
|
||||
|
||||
fiona.setName("fo2");
|
||||
fiona.setLocalDate(LocalDate.now());
|
||||
fiona.update();
|
||||
|
||||
byNames = findByNames("Jack", "Rob", "Moby");
|
||||
assertThat(byNames).hasSize(3);
|
||||
|
||||
Thread.sleep(200);
|
||||
|
||||
one2 = nameStartsWith("fo%");
|
||||
System.out.println("one2 " + one2);
|
||||
one2 = nameStartsWith("f0%");
|
||||
|
||||
System.out.println("one2 " + one2);
|
||||
|
||||
DB.getServerCacheManager().clear(Person.class);
|
||||
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
private List<Person> insertSomePeople() {
|
||||
|
||||
List<Person> people = new ArrayList<>();
|
||||
for (String name : new String[]{"Jack", "John", "Rob", "Moby", "Fiona"}) {
|
||||
people.add(new Person(name));
|
||||
}
|
||||
|
||||
DB.saveAll(people);
|
||||
return people;
|
||||
}
|
||||
|
||||
private Person findByName(String name) {
|
||||
return new QPerson()
|
||||
.name.eq(name)
|
||||
.findOne();
|
||||
}
|
||||
|
||||
private List<Person> findByNames(String... names) {
|
||||
return new QPerson()
|
||||
.name.in(names)
|
||||
.setUseCache(true)
|
||||
.findList();
|
||||
}
|
||||
|
||||
private Person findById(int id) {
|
||||
return new QPerson()
|
||||
.id.eq(id)
|
||||
.findOne();
|
||||
}
|
||||
|
||||
private List<Person> nameStartsWith(String pattern) {
|
||||
return new QPerson()
|
||||
.name.istartsWith(pattern)
|
||||
.setUseQueryCache(true)
|
||||
.findList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
ebean:
|
||||
dumpMetricsOnShutdown: true
|
||||
dumpMetricsOptions: sql,hash,loc
|
||||
test:
|
||||
redis: latest
|
||||
# shutdown: stop # stop | remove
|
||||
platform: h2 # h2, postgres, mysql, oracle, sqlserver, sqlite
|
||||
ddlMode: dropCreate # none | dropCreate | create | migration | createOnly | migrationDropCreate
|
||||
dbName: myapp
|
||||
@@ -0,0 +1,29 @@
|
||||
<configuration scan="true" scanPeriod="10 seconds">
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>TRACE</level>
|
||||
</filter>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="WARN">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<!--<logger name="com.avaje.ebeaninternal.server.cluster" level="TRACE"/>-->
|
||||
|
||||
<!-- <logger name="io.ebean" level="DEBUG"/>-->
|
||||
|
||||
<logger name="io.ebean.SQL" level="TRACE"/>
|
||||
<logger name="io.ebean.TXN" level="TRACE"/>
|
||||
<logger name="io.ebean.SUM" level="TRACE"/>
|
||||
|
||||
<logger name="io.ebean.cache" level="TRACE"/>
|
||||
<logger name="io.ebean.cache.QUERY" level="TRACE"/>
|
||||
<logger name="io.ebean.cache.BEAN" level="TRACE"/>
|
||||
<logger name="io.ebean.cache.COLL" level="TRACE"/>
|
||||
<logger name="io.ebean.cache.NATKEY" level="TRACE"/>
|
||||
|
||||
</configuration>
|
||||
+5
-5
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
<!-- <parent>-->
|
||||
<!-- <groupId>org.avaje</groupId>-->
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-parent-12.6.2</tag>
|
||||
<tag>ebean-parent-12.6.6</tag>
|
||||
</scm>
|
||||
|
||||
<name>ebean test</name>
|
||||
@@ -43,14 +43,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -91,7 +91,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.2.12</version>
|
||||
<version>42.2.18</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebean.test;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.migration.util.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.ebean.test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Utilities for IO.
|
||||
*/
|
||||
class IOUtils {
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and return them as UTF-8 string.
|
||||
*/
|
||||
static String readUtf8(InputStream in) throws IOException {
|
||||
return bytesToUtf8(read(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire contents of the specified input stream and returns them as a byte array.
|
||||
*/
|
||||
private static byte[] read(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
pump(in, buffer);
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 string corresponding to the specified bytes.
|
||||
*/
|
||||
private static String bytesToUtf8(byte[] data) {
|
||||
return new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the specified input stream and copies it to the specified
|
||||
* output stream, until the input stream is at EOF. Both streams are then
|
||||
* closed.
|
||||
*/
|
||||
private static void pump(InputStream in, OutputStream out) throws IOException {
|
||||
if (in == null) throw new IOException("Input stream is null");
|
||||
if (out == null) throw new IOException("Output stream is null");
|
||||
try {
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
for (; ; ) {
|
||||
int bytes = in.read(buffer);
|
||||
if (bytes < 0) {
|
||||
break;
|
||||
}
|
||||
out.write(buffer, 0, bytes);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,18 @@
|
||||
datasource:
|
||||
db:
|
||||
username: sa
|
||||
password:
|
||||
password: sa
|
||||
databaseUrl: jdbc:h2:mem:tests
|
||||
|
||||
pg:
|
||||
username: sa
|
||||
password:
|
||||
password: sa
|
||||
databaseUrl: jdbc:h2:mem:tests
|
||||
|
||||
ebean:
|
||||
# migration:
|
||||
# run: false
|
||||
#
|
||||
# ddl:
|
||||
# generate: true
|
||||
# run: true
|
||||
## createOnly: true
|
||||
ddl:
|
||||
generate: true
|
||||
run: true
|
||||
|
||||
docstore:
|
||||
url: http://127.0.0.1:9200
|
||||
@@ -26,57 +22,9 @@ ebean:
|
||||
# useDocker: true
|
||||
elasticVersion: 5.6
|
||||
|
||||
# create: true
|
||||
|
||||
test:
|
||||
redis: latest
|
||||
platform: mariadb #, postgres, mysql, mariadb, oracle, sqlserver, hana
|
||||
# useDocker: false
|
||||
# dockerMode: dropCreate
|
||||
ddlMode: dropCreate # none | dropCreate | create | migrations
|
||||
dbName: junk
|
||||
postgres:
|
||||
version: 9.6
|
||||
extensions: pgcryto, hstore
|
||||
username: asd
|
||||
password: test
|
||||
url: asd
|
||||
driver: asd
|
||||
|
||||
|
||||
|
||||
docker:
|
||||
postgres:
|
||||
# port: 6432
|
||||
version: 9.6
|
||||
extensions: pgcryto, hstore
|
||||
username: ${test_db}
|
||||
password: test
|
||||
databaseUrl: jdbc:postgresql://localhost:6432/${test_db}
|
||||
databaseDriver: org.postgresql.Driver
|
||||
|
||||
mysql:
|
||||
version: 5.6
|
||||
username: ${test_db}
|
||||
password: test
|
||||
databaseUrl: jdbc:mysql://localhost:4306/${test_db}
|
||||
databaseDriver: com.mysql.jdbc.Driver
|
||||
|
||||
sqlserver:
|
||||
version: 2017-CE
|
||||
username: ${test_db}
|
||||
password: SqlS3rv#r
|
||||
databaseUrl: jdbc:sqlserver://localhost:1433;databaseName=${test_db}
|
||||
databaseDriver: com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
oracle:
|
||||
username: ${test_db}
|
||||
password: test
|
||||
databaseUrl: jdbc:oracle:thin:@127.0.0.1:1521:XE
|
||||
databaseDriver: oracle.jdbc.driver.OracleDriver
|
||||
|
||||
hana:
|
||||
username: ${test_db}
|
||||
password: HXEHana1
|
||||
databaseUrl: jdbc:sap://localhost:39017/?databaseName=HXE
|
||||
databaseDriver: com.sap.db.jdbc.Driver
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
postgres.version=9.6
|
||||
postgres.dbName=test_db
|
||||
postgres.dbUser=test_user
|
||||
postgres.dbPassword=test
|
||||
postgres.dbExtensions=hstore,pgcrypto
|
||||
|
||||
|
||||
sqlserver.version=2017-CU2
|
||||
#sqlserver.port=1433
|
||||
#sqlserver.dbName=test_db
|
||||
#sqlserver.dbUser=test_user
|
||||
#sqlserver.dbPassword=SqlS3rv#r
|
||||
|
||||
|
||||
hana.version=2.00.033.00.20180925.2
|
||||
hana.port=39117
|
||||
hana.instanceNumber=91
|
||||
hana.passwordsUrl=file:///hana/mounts/passwords.json
|
||||
hana.mountsDirectory=/data/dockermounts
|
||||
# agree to the SAP license (https://www.sap.com/docs/download/cmp/2016/06/sap-hana-express-dev-agmt-and-exhibit.pdf)
|
||||
hana.agreeToSapLicense=false
|
||||
+6
-6
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean composite</name>
|
||||
@@ -22,34 +22,34 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Technically optional but most expected to use query beans -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- In future ebean-ddl-generator would only be dependency of ebean-test -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- In future this is optional and not included by default -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-autotune</artifactId>
|
||||
<version>12.6.2</version>
|
||||
<version>12.6.6</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user