Merge branch 'master' into ebean-15x

This commit is contained in:
Rob Bygrave
2024-02-19 22:43:41 +13:00
181 changed files with 2746 additions and 915 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<artifactId>composites</artifactId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean api</name>
@@ -39,7 +39,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-config</artifactId>
<version>3.9</version>
<version>3.10</version>
</dependency>
<dependency>
-49
View File
@@ -266,55 +266,6 @@ public final class DB {
getDefault().register(transactionCallback);
}
/**
* Deprecated for removal migrate using try-with-resources and commit on the transaction itself.
* <p>
* Commit the current transaction.
*/
@Deprecated(forRemoval = true)
public static void commitTransaction() {
getDefault().commitTransaction();
}
/**
* Deprecated for removal migrate to using try-with-resources and rollback on the transaction itself.
* <p>
* Rollback the current transaction.
*/
@Deprecated(forRemoval = true)
public static void rollbackTransaction() {
getDefault().rollbackTransaction();
}
/**
* If the current transaction has already been committed do nothing otherwise
* rollback the transaction.
* <p>
* It is preferable to use <em>try with resources</em> rather than this.
* <p>
* Useful to put in a finally block to ensure the transaction is ended, rather
* than a rollbackTransaction() in each catch block.
* <p>
* Code example:
*
* <pre>{@code
* DB.beginTransaction();
* try {
* // do some fetching and or persisting
*
* // commit at the end
* DB.commitTransaction();
*
* } finally {
* // if commit didn't occur then rollback the transaction
* DB.endTransaction();
* }
* }</pre>
*/
public static void endTransaction() {
getDefault().endTransaction();
}
/**
* Mark the current transaction as rollback only.
*/
@@ -0,0 +1,104 @@
package io.ebean;
final class DInsertOptionsBuilder implements InsertOptions.Builder {
private Boolean getGeneratedKeys;
private boolean onConflictUpdate;
private boolean onConflictNothing;
private String constraint;
private String uniqueColumns;
private String updateSet;
@Override
public InsertOptions.Builder onConflictNothing() {
this.onConflictNothing = true;
return this;
}
@Override
public InsertOptions.Builder onConflictUpdate() {
this.onConflictUpdate = true;
return this;
}
@Override
public InsertOptions.Builder constraint(String constraint) {
this.constraint = constraint;
return this;
}
@Override
public InsertOptions.Builder uniqueColumns(String uniqueColumns) {
this.uniqueColumns = uniqueColumns;
return this;
}
@Override
public InsertOptions.Builder updateSet(String updateSet) {
this.updateSet = updateSet;
return this;
}
@Override
public InsertOptions.Builder getGeneratedKeys(boolean getGeneratedKeys) {
this.getGeneratedKeys = getGeneratedKeys;
return this;
}
@Override
public InsertOptions build() {
return new Options(constraint, uniqueColumns, updateSet, onConflictUpdate, onConflictNothing, getGeneratedKeys);
}
static final class Options implements InsertOptions {
private static final String UPDATE = "U";
private static final String NOTHING = "N";
private static final String NORMAL = "_";
private final String key;
private final Boolean getGeneratedKeys;
private final String constraint;
private final String uniqueColumns;
private final String updateSet;
Options(String constraint, String uniqueColumns, String updateSet, boolean onConflictUpdate, boolean onConflictNothing, Boolean getGeneratedKeys) {
this.constraint = constraint;
this.uniqueColumns = uniqueColumns;
this.updateSet = updateSet;
this.getGeneratedKeys = getGeneratedKeys;
this.key = (onConflictUpdate ? UPDATE : onConflictNothing ? NOTHING : NORMAL)
+ '+' + plus(constraint)
+ '+' + plus(uniqueColumns)
+ '+' + plus(updateSet);
}
private String plus(String val) {
return val == null ? "" : val;
}
@Override
public String key() {
return key;
}
@Override
public String constraint() {
return constraint;
}
@Override
public String uniqueColumns() {
return uniqueColumns;
}
@Override
public String updateSet() {
return updateSet;
}
@Override
public Boolean getGetGeneratedKeys() {
return getGeneratedKeys;
}
}
}
+31 -43
View File
@@ -607,49 +607,6 @@ public interface Database {
*/
void flush();
/**
* Deprecated for removal migrate using try-with-resources and commit on the transaction itself.
* <p>
* Commit the current transaction.
*/
@Deprecated(forRemoval = true)
void commitTransaction();
/**
* Deprecated for removal migrate to using try-with-resources and rollback on the transaction itself.
* <p>
* Rollback the current transaction.
*/
@Deprecated(forRemoval = true)
void rollbackTransaction();
/**
* If the current transaction has already been committed do nothing otherwise
* rollback the transaction.
* <p>
* Useful to put in a finally block to ensure the transaction is ended, rather
* than a rollbackTransaction() in each catch block.
* <p>
* Code example:
* <p>
* <pre>{@code
*
* database.beginTransaction();
* try {
* // do some fetching and or persisting ...
*
* // commit at the end
* database.commitTransaction();
*
* } finally {
* // if commit didn't occur then rollback the transaction
* database.endTransaction();
* }
*
* }</pre>
*/
void endTransaction();
/**
* Refresh the values of a bean.
* <p>
@@ -1168,22 +1125,53 @@ public interface Database {
*/
void insert(Object bean);
/**
* Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING).
* <p>
* Currently, this is limited to use with Postgres only,
* <p>
* When using this ebean will look to determine the unique columns by looking at
* the mapping like {@code @Column(unique=true} and {@code @Index(unique=true}.
*/
void insert(Object bean, InsertOptions insertOptions);
/**
* Insert the bean with a transaction.
*/
void insert(Object bean, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insert(Object bean, InsertOptions insertOptions, Transaction transaction);
/**
* Insert a collection of beans. If there is no current transaction one is created and used to
* insert all the beans in the collection.
*/
void insertAll(Collection<?> beans);
/**
* Insert the beans with options - typically ON CONFLICT DO UPDATE | DO NOTHING.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options);
/**
* Insert a collection of beans with an explicit transaction.
*/
void insertAll(Collection<?> beans, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction);
/**
* Execute explicitly passing a transaction.
*/
@@ -1009,6 +1009,14 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setSkipDataSourceCheck(boolean skipDataSourceCheck);
/**
* Set to true if this database is used in a read only way.
* <p>
* The DataSource and read-only DataSource are expected to be the same
* and use readOnly=true and autoCommit=true.
*/
DatabaseBuilder readOnlyDatabase(boolean readOnlyDatabase);
/**
* Set a DataSource.
*/
@@ -2543,6 +2551,14 @@ public interface DatabaseBuilder {
*/
boolean skipDataSourceCheck();
/**
* Return true if this database is used in a read only way.
* <p>
* The DataSource and read-only DataSource are expected to be the same
* and use readOnly=true and autoCommit=true.
*/
boolean readOnlyDatabase();
/**
* Return the DataSource.
*/
@@ -3,10 +3,9 @@ package io.ebean;
import io.ebean.config.ContainerConfig;
import io.ebean.service.SpiContainer;
import io.ebean.service.SpiContainerFactory;
import jakarta.persistence.PersistenceException;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.concurrent.locks.ReentrantLock;
@@ -146,12 +145,9 @@ public final class DatabaseFactory {
if (container != null) {
return container;
}
if (containerConfig == null) {
// effectively load configuration from ebean.properties
Properties properties = DbPrimary.getProperties();
containerConfig = new ContainerConfig();
containerConfig.loadFromProperties(properties);
}
container = createContainer(containerConfig);
return container;
@@ -160,7 +156,7 @@ public final class DatabaseFactory {
/**
* Create the container instance using the configuration.
*/
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
private static SpiContainer createContainer(ContainerConfig containerConfig) {
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
if (factories.hasNext()) {
return factories.next().create(containerConfig);
@@ -44,25 +44,12 @@ final class DbPrimary {
* Return the default database name.
*/
static String getDefaultServerName() {
lock.lock();
try {
getProperties();
return defaultServerName;
} finally {
lock.unlock();
}
}
/**
* Return the default configuration Properties.
*/
static Properties getProperties() {
lock.lock();
try {
if (defaultServerName == null) {
defaultServerName = determineDefaultServerName();
}
return Config.asProperties();
return defaultServerName;
} finally {
lock.unlock();
}
@@ -3,6 +3,8 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -206,14 +208,19 @@ public interface DtoQuery<T> extends CancelableQuery {
*/
DtoQuery<T> usingTransaction(Transaction transaction);
/**
* Execute the query using the given connection.
*/
DtoQuery<T> usingConnection(Connection connection);
/**
* Ensure that the master DataSource is used if there is a read only data source
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)} then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
DtoQuery<T> usingMaster();
@@ -0,0 +1,119 @@
package io.ebean;
import io.avaje.lang.Nullable;
/**
* Options to be used with insert such as ON CONFLICT DO UPDATE | NOTHING.
*/
public interface InsertOptions {
/**
* Use ON CONFLICT UPDATE with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_UPDATE = InsertOptions.builder()
.onConflictUpdate()
.build();
/**
* Use ON CONFLICT DO NOTHING with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_NOTHING = InsertOptions.builder()
.onConflictNothing()
.build();
/**
* Return a builder for InsertOptions.
*/
static Builder builder() {
return new DInsertOptionsBuilder();
}
/**
* Return the constraint name that is used for ON CONFLICT.
*/
@Nullable
String constraint();
/**
* Return the unique columns that is used for ON CONFLICT.
* <p>
* When not explicitly set will use mapping like {@code @Column(unique=true)} to determine the
* non-unique columns.
*/
@Nullable
String uniqueColumns();
/**
* Return the ON CONFLICT UPDATE SET clause.
* <p>
* When not set will use the non-unique columns.
*/
@Nullable
String updateSet();
/**
* Return if GetGeneratedKeys should be used to fetch the generated keys after insert.
*/
@Nullable
Boolean getGetGeneratedKeys();
/**
* Return the key for these build options.
*/
String key();
/**
* The builder for InsertOptions.
*/
interface Builder {
/**
* Use a ON CONFLICT UPDATE automatically determining the unique columns.
*/
Builder onConflictUpdate();
/**
* Use a ON CONFLICT DO NOTHING automatically determining the unique columns.
*/
Builder onConflictNothing();
/**
* Specify an explicit conflict constraint name.
* <p>
* When this is used then unique columns will not be used.
*/
Builder constraint(String constraint);
/**
* Specify the unique columns for the conflict target.
* <p>
* When not specified and constraint is also not specified then
* it will automatically determine the unique columns
* based on mapping like {@code @Column(unique=true)} and
* {@code @Index(unique=true)} .
*/
Builder uniqueColumns(String uniqueColumns);
/**
* Specify the ON CONFLICT DO UPDATE SET clause.
* <p>
* When not specified ebean will include all the non-unique columns.
*/
Builder updateSet(String updateSet);
/**
* Specify if GetGeneratedKeys should be used to return generated keys.
*/
Builder getGeneratedKeys(boolean getGeneratedKeys);
/**
* Build and return the insert options.
*/
InsertOptions build();
}
}
+21 -2
View File
@@ -650,9 +650,9 @@ public interface Query<T> extends CancelableQuery {
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)} then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
Query<T> usingMaster();
@@ -918,6 +918,25 @@ public interface Query<T> extends CancelableQuery {
@Nullable
<A> A findSingleAttribute();
/**
* Execute the query returning a single optional attribute value.
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* Optional<String> maybeName =
* new QCustomer()
* .select(name)
* .id.eq(42)
* .status.eq(NEW)
* .findSingleAttributeOrEmpty();
*
* }</pre>
*
* @return an optional value for the selected property
*/
<A> Optional<A> findSingleAttributeOrEmpty();
/**
* Return true if this is countDistinct query.
*/
@@ -3,7 +3,9 @@ package io.ebean;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -45,14 +47,19 @@ public interface SqlQuery extends Serializable, CancelableQuery {
*/
SqlQuery usingTransaction(Transaction transaction);
/**
* Execute the query using the given connection.
*/
SqlQuery usingConnection(Connection connection);
/**
* Ensure that the master DataSource is used if there is a read only data source
* being used (that is using a read replica database potentially with replication lag).
* <p>
* When the database is configured with a read-only DataSource via
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)}then
* by default when a query is run without an active transaction, it uses the read-only data
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
* source. We use {@code usingMaster()} to instead ensure that the query is executed
* against the master data source.
*/
SqlQuery usingMaster();
@@ -1,5 +1,8 @@
package io.ebean.config;
import io.avaje.config.Config;
import io.avaje.config.Configuration;
import java.util.Properties;
/**
@@ -15,8 +18,17 @@ public class ContainerConfig {
private String namespace;
private String podName;
private int port;
private Properties properties;
private Configuration configuration;
public ContainerConfig() {
this.configuration = Config.asConfiguration();
this.active = configuration.getBool("ebean.cluster.active", active);
this.serviceName = configuration.getNullable("ebean.cluster.serviceName", serviceName);
this.namespace = configuration.getNullable("ebean.cluster.namespace", namespace);
this.podName = configuration.getNullable("ebean.cluster.podName", podName);
this.port = configuration.getInt("ebean.cluster.port", 0);
}
/**
* Return the service name.
@@ -92,7 +104,7 @@ public class ContainerConfig {
* Return the deployment properties.
*/
public Properties getProperties() {
return properties;
return properties != null ? properties : configuration.asProperties();
}
/**
@@ -102,26 +114,4 @@ public class ContainerConfig {
this.properties = properties;
}
/**
* Load the settings from properties.
*/
public void loadFromProperties(Properties properties) {
this.properties = properties;
this.active = getProperty(properties, "ebean.cluster.active", active);
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
this.podName = properties.getProperty("ebean.cluster.podName", podName);
String portParam = properties.getProperty("ebean.cluster.port");
if (portParam != null) {
this.port = Integer.parseInt(portParam);
}
}
/**
* Return the boolean property setting.
*/
protected boolean getProperty(Properties properties, String key, boolean defaultValue) {
return "true".equalsIgnoreCase(properties.getProperty(key, Boolean.toString(defaultValue)));
}
}
@@ -224,7 +224,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private PersistBatch persistBatchOnCascade = PersistBatch.INHERIT;
private int persistBatchSize = 20;
private int persistBatchSize = 100;
private EnumType defaultEnumType = EnumType.ORDINAL;
@@ -292,6 +292,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
private boolean skipDataSourceCheck;
private boolean readOnlyDatabase;
/**
* The data source (if programmatically provided).
*/
@@ -1276,7 +1278,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
@Override
public boolean skipDataSourceCheck() {
return skipDataSourceCheck;
return skipDataSourceCheck || readOnlyDatabase;
}
@Override
@@ -1285,6 +1287,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public DatabaseBuilder readOnlyDatabase(boolean readOnlyDatabase) {
this.readOnlyDatabase = readOnlyDatabase;
return this;
}
@Override
public boolean readOnlyDatabase() {
return readOnlyDatabase;
}
@Override
public DataSource getDataSource() {
return dataSource;
@@ -2045,6 +2058,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
loadDataSourceSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
readOnlyDatabase = p.getBoolean("readOnlyDatabase", readOnlyDatabase);
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
@@ -21,9 +21,4 @@ class DbPrimaryTest {
assertEquals("h2", DbPrimary.getDefaultServerName());
}
@Test
void testLoadProperties() {
Properties properties = DbPrimary.getProperties();
assertFalse(properties.isEmpty());
}
}
@@ -74,6 +74,7 @@ class DatabaseConfigTest {
props.setProperty("forUpdateNoKey", "true");
props.setProperty("defaultServer", "false");
props.setProperty("skipDataSourceCheck", "true");
props.setProperty("readOnlyDatabase", "true");
props.setProperty("queryPlan.enable", "true");
props.setProperty("queryPlan.thresholdMicros", "10000");
@@ -93,6 +94,7 @@ class DatabaseConfigTest {
assertTrue(settings.isAutoLoadModuleInfo());
assertTrue(settings.isLoadModuleInfo());
assertTrue(settings.skipDataSourceCheck());
assertTrue(settings.readOnlyDatabase());
assertTrue(settings.isIdGeneratorAutomatic());
assertFalse(settings.getPlatformConfig().isCaseSensitiveCollation());
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -46,7 +46,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<!--
@@ -147,21 +147,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
@@ -3,6 +3,8 @@ package io.ebeaninternal.api;
import io.ebean.TxScope;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Used internally to handle the scoping of transactions for methods.
@@ -47,6 +49,11 @@ public final class ScopeTrans {
private boolean nestedCommit;
private boolean nestedUseSavepoint;
/**
* The UserObjects when using in nested transactions.
*/
private Map<String, Object> userObjects;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
this.rollbackOnChecked = rollbackOnChecked;
this.created = created;
@@ -218,4 +225,17 @@ public final class ScopeTrans {
return e instanceof RuntimeException || rollbackOnChecked;
}
public void putUserObject(String name, Object value) {
if (userObjects == null) {
userObjects = new HashMap<>();
}
userObjects.put(name, value);
}
public Object getUserObject(String name) {
if (userObjects == null) {
return null;
}
return userObjects.get(name);
}
}
@@ -2,7 +2,6 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
import io.ebeaninternal.server.util.ArrayStack;
import jakarta.persistence.PersistenceException;
/**
@@ -173,4 +172,31 @@ public final class ScopedTransaction extends SpiTransactionProxy {
return current.caughtThrowable(e);
}
/**
* New user objects are always written to the current ScopeTrans.
*/
@Override
public void putUserObject(String name, Object value) {
current.putUserObject(name, value);
}
/**
* Returns the userObject in the stack, Herew we search
* the stack and return the first found userObject
*/
@Override
public Object getUserObject(String name) {
Object obj = current.getUserObject(name);
if (obj != null) {
return obj;
}
for (ScopeTrans trans : stack) {
obj = trans.getUserObject(name);
if (obj != null) {
return obj;
}
}
return transaction.getUserObject(name);
}
}
@@ -84,6 +84,11 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
*/
SpiTransactionManager transactionManager();
/**
* End the current transaction if it is active.
*/
void endTransaction();
/**
* Return all the descriptors.
*/
@@ -627,6 +627,16 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ObjectGraphNode parentNode();
/**
* Set that this is a future query that will execute in the background.
*/
void usingFuture();
/**
* Return true if this is a future query.
*/
boolean isUsingFuture();
/**
* Return false when this is a lazy load or refresh query for a bean.
* <p>
@@ -5,7 +5,6 @@ import io.ebean.Database;
import io.ebean.config.ContainerConfig;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
@@ -20,38 +19,22 @@ public class ClusterManager implements ServerLookup {
private static final System.Logger clusterLogger = AppLog.getLogger("io.ebean.Cluster");
private final ReentrantLock lock = new ReentrantLock();
private final ConcurrentHashMap<String, Database> serverMap = new ConcurrentHashMap<>();
private final Object monitor = new Object();
private final ClusterBroadcast broadcast;
private boolean started;
private boolean shutdown;
public ClusterManager(ContainerConfig config) {
ClusterBroadcastFactory factory = createFactory();
if (factory != null && config.isActive()) {
broadcast = factory.create(this, config);
if (config.isActive()) {
ClusterBroadcastFactory factory = createFactory();
broadcast = factory != null ? factory.create(this, config) : null;
} else {
broadcast = null;
}
}
/**
* Return the ClusterTransportFactory via ServiceLoader.
*/
private ClusterBroadcastFactory createFactory() {
ServiceLoader<ClusterBroadcastFactory> load = ServiceLoader.load(ClusterBroadcastFactory.class);
ClusterBroadcastFactory factory = null;
Iterator<ClusterBroadcastFactory> iterator = load.iterator();
if (iterator.hasNext()) {
factory = iterator.next();
}
return factory;
return ServiceLoader.load(ClusterBroadcastFactory.class).findFirst().orElse(null);
}
public void registerServer(Database server) {
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.Database;
import io.ebean.DatabaseBuilder;
import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -13,11 +14,13 @@ import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.executor.DefaultBackgroundExecutor;
import jakarta.persistence.PersistenceException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
@@ -33,12 +36,22 @@ public final class DefaultContainer implements SpiContainer {
private final ReentrantLock lock = new ReentrantLock();
private final ClusterManager clusterManager;
private final List<EntityClassRegister> entityClassRegisters;
public DefaultContainer(ContainerConfig containerConfig) {
this.clusterManager = new ClusterManager(containerConfig);
// register so that we can shutdown any Ebean wide
// resources such as clustering
ShutdownManager.registerContainer(this);
entityClassRegisters = initEntityRegisters();
}
private List<EntityClassRegister> initEntityRegisters() {
var entityClassRegisters = new ArrayList<EntityClassRegister>();
for (EntityClassRegister entityClassRegister : ServiceLoader.load(EntityClassRegister.class)) {
entityClassRegisters.add(entityClassRegister);
}
return entityClassRegisters;
}
@Override
@@ -52,10 +65,8 @@ public final class DefaultContainer implements SpiContainer {
*/
@Override
public SpiEbeanServer createServer(String name) {
DatabaseBuilder config = new DatabaseConfig();
config.setName(name);
config.loadFromProperties();
return createServer(config);
var builder = Database.builder().name(name).loadFromProperties();
return createServer(builder);
}
private SpiBackgroundExecutor createBackgroundExecutor(DatabaseBuilder builder) {
@@ -122,12 +133,10 @@ public final class DefaultContainer implements SpiContainer {
}
if (config.isLoadModuleInfo()) {
// auto register entity classes
boolean found = false;
for (EntityClassRegister loader : ServiceLoader.load(EntityClassRegister.class)) {
for (EntityClassRegister loader : entityClassRegisters) {
config.addAll(loader.classesFor(config.getName(), config.isDefaultServer()));
found = true;
}
if (!found) {
if (entityClassRegisters.isEmpty()) {
checkMissingModulePathProvides();
}
}
@@ -189,7 +198,7 @@ public final class DefaultContainer implements SpiContainer {
*/
private void setNamingConvention(DatabaseBuilder.Settings config) {
if (config.getNamingConvention() == null) {
config.setNamingConvention(new UnderscoreNamingConvention());
config.namingConvention(new UnderscoreNamingConvention());
}
}
@@ -204,7 +213,7 @@ public final class DefaultContainer implements SpiContainer {
}
// automatically determine the platform
platform = new DatabasePlatformFactory().create(config);
config.setDatabasePlatform(platform);
config.databasePlatform(platform);
}
platform.configure(config.getPlatformConfig());
}
@@ -228,11 +237,9 @@ public final class DefaultContainer implements SpiContainer {
* Check the autoCommit and Transaction Isolation levels of the DataSource.
* <p>
* If autoCommit is true this could be a real problem.
* </p>
* <p>
* If the Isolation level is not READ_COMMITTED then optimistic concurrency
* checking may not work as expected.
* </p>
*/
private boolean checkDataSource(DatabaseBuilder.Settings config) {
if (isOfflineMode(config)) {
@@ -714,16 +714,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
currentTransaction().flush();
}
@Override
public void commitTransaction() {
currentTransaction().commit();
}
@Override
public void rollbackTransaction() {
currentTransaction().rollback();
}
@Override
public void endTransaction() {
Transaction transaction = transactionManager.inScope();
@@ -885,7 +875,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
transaction = currentServerTransaction();
}
if (!query.isRawSql()) {
if (query.isAutoTunable() && !autoTuneService.tuneQuery(query)) {
if (!query.isAutoTunable() || !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
@@ -1194,6 +1184,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureRowCount<T> findFutureCount(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
copy.usingFuture();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
@@ -1511,41 +1502,50 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}, transaction);
}
/**
* Insert the bean.
*/
@Override
public void insert(Object bean) {
insert(bean, null);
persister.insert(checkEntityBean(bean), null, null);
}
@Override
public void insert(Object bean, @Nullable InsertOptions insertOptions) {
persister.insert(checkEntityBean(bean), insertOptions, null);
}
/**
* Insert the bean with a transaction.
*/
@Override
public void insert(Object bean, @Nullable Transaction transaction) {
persister.insert(checkEntityBean(bean), transaction);
persister.insert(checkEntityBean(bean), null, transaction);
}
@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
persister.insert(checkEntityBean(bean), insertOptions, transaction);
}
/**
* Insert all beans in the collection.
*/
@Override
public void insertAll(Collection<?> beans) {
insertAll(beans, null);
insertAll(beans, null, null);
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
insertAll(beans, options, null);
}
/**
* Insert all beans in the collection with a transaction.
*/
@Override
public void insertAll(@Nullable Collection<?> beans, @Nullable Transaction transaction) {
insertAll(beans, null, transaction);
}
@Override
public void insertAll(@Nullable Collection<?> beans, InsertOptions options, @Nullable Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), txn);
persister.insert(checkEntityBean(bean), options, txn);
}
return 0;
}, transaction);
@@ -38,15 +38,19 @@ final class InitDataSource {
* Initialise the "main" read write DataSource from configuration.
*/
private DataSource initDataSource() {
return createFromConfig(config.getDataSourceConfig(), false);
return createFromConfig(config.getDataSourceConfig(), config.readOnlyDatabase(), false);
}
/**
* Initialise the "read only" DataSource from configuration.
*/
private DataSource initReadOnlyDataSource() {
if (config.readOnlyDatabase()) {
// using the same DataSource instance
return config.getDataSource();
}
var roConfig = readOnlyConfig();
return roConfig == null ? null : createFromConfig(roConfig, true);
return roConfig == null ? null : createFromConfig(roConfig, false, true);
}
DataSourceBuilder.Settings readOnlyConfig() {
@@ -76,20 +80,21 @@ final class InitDataSource {
return url != null && !"none".equalsIgnoreCase(url) && !url.trim().isEmpty();
}
private DataSource createFromConfig(DataSourceBuilder.Settings dsConfig, boolean readOnly) {
private DataSource createFromConfig(DataSourceBuilder.Settings dsConfig, boolean readOnlyDB, boolean readOnly) {
if (dsConfig == null) {
throw new PersistenceException("No DataSourceBuilder defined for " + config.getName());
}
if (dsConfig.isOffline()) {
if (config.getDatabasePlatformName() == null) {
throw new PersistenceException("You MUST specify a DatabasePlatformName on DatabaseConfig when offline");
}
if (dsConfig.isOffline() && config.getDatabasePlatformName() == null) {
throw new PersistenceException("You MUST specify a DatabasePlatformName on DatabaseConfig when offline");
}
attachAlert(dsConfig);
attachListener(dsConfig);
if (readOnly) {
if (readOnlyDB) {
dsConfig.autoCommit(true);
dsConfig.readOnly(true);
} else if (readOnly) {
// setup to use AutoCommit such that we skip explicit commit
var mainSettings = config.getDataSourceConfig();
dsConfig.autoCommit(true);
@@ -35,7 +35,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private final OrmQueryEngine queryEngine;
private final SpiQuery<T> query;
private final BeanFindController finder;
private final Boolean readOnly;
private LoadContext loadContext;
private PersistenceContext persistenceContext;
private HashQuery cacheKey;
@@ -52,7 +51,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
this.finder = beanDescriptor.beanFinder();
this.queryEngine = queryEngine;
this.query = query;
this.readOnly = query.isReadOnly();
this.persistenceContext = query.persistenceContext();
}
@@ -134,13 +132,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return loadContext.secondaryQueriesMinBatchSize();
}
/**
* Return the Normal, sharedInstance, ReadOnly state of this query.
*/
public Boolean isReadOnly() {
return readOnly;
}
/**
* Return the BeanDescriptor for the associated bean.
*/
@@ -720,4 +711,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
public int forwardOnlyFetchSize() {
return queryEngine.forwardOnlyFetchSize();
}
public void clearContext() {
if (!transaction.isAutoPersistUpdates()) {
beanDescriptor.contextClear(transaction.persistenceContext());
}
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PreGetterCallback;
@@ -19,8 +19,6 @@ import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.*;
@@ -119,6 +117,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Many-to-many intersection table changes that are held for later batch processing.
*/
private List<SaveMany> saveMany;
private InsertOptions insertOptions;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -350,6 +349,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.dirtyPropertyNames();
}
public boolean isChangedProperty(int propertyIndex) {
if (dirtyProperties == null) {
return intercept.isChangedProperty(propertyIndex);
} else {
return dirtyProperties[propertyIndex];
}
}
/**
* Return the dirty properties on this request.
*/
@@ -397,7 +404,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return isNotifyPersistListener();
}
private boolean isNotifyPersistListener() {
return beanPersistListener != null;
}
@@ -757,7 +763,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE && (isChangeLog || notifyCache)) {
if (type == Type.UPDATE) {
// get the dirty properties for update notification to the doc store
dirtyProperties = intercept.dirtyProperties();
}
@@ -1236,4 +1242,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void setGeneratedId() {
beanDescriptor.setGeneratedId(entityBean, transaction);
}
public void setInsertOptions(InsertOptions insertOptions) {
this.insertOptions = insertOptions;
}
public InsertOptions insertOptions() {
return insertOptions;
}
}
@@ -77,10 +77,13 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
*/
@Override
public void postExecute() {
OrmUpdateType ormUpdateType = ormUpdate.ormUpdateType();
if (OrmUpdateType.INSERT != ormUpdateType && !transaction.isAutoPersistUpdates()) {
beanDescriptor.contextClear(transaction.persistenceContext());
}
if (startNanos > 0) {
persistExecute.collectOrmUpdate(label, startNanos);
}
OrmUpdateType ormUpdateType = ormUpdate.ormUpdateType();
String tableName = ormUpdate.baseTable();
if (transaction.isLogSummary()) {
transaction.logSummary("{0} table[{1}] rows[{2}] bind[{3}]", ormUpdateType, tableName, rowCount, bindLog);
@@ -3,10 +3,13 @@ package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.persist.TrimLogSql;
import java.util.List;
/**
* Persist request specifically for CallableSql.
*/
@@ -156,6 +159,14 @@ public final class PersistRequestUpdateSql extends PersistRequest {
*/
@Override
public void postExecute() {
if (sqlType != SqlType.SQL_INSERT && !transaction.isAutoPersistUpdates()) {
List<BeanDescriptor<?>> descriptors = server.descriptors(tableName);
if (descriptors != null) {
for (BeanDescriptor<?> descriptor : descriptors) {
descriptor.contextClear(transaction.persistenceContext());
}
}
}
if (startNanos > 0) {
persistExecute.collectSqlUpdate(label, startNanos);
}
@@ -1,11 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.CallableSql;
import io.ebean.MergeOptions;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.Update;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
@@ -31,9 +27,9 @@ public interface Persister {
void update(EntityBean entityBean, Transaction t);
/**
* Force an Insert using the given bean.
* Perform an Insert using the given bean.
*/
void insert(EntityBean entityBean, Transaction t);
void insert(EntityBean entityBean, @Nullable InsertOptions insertOptions, @Nullable Transaction t);
/**
* Insert or update the bean depending on its state.
@@ -44,7 +40,6 @@ public interface Persister {
* Delete a bean given it's type and id value.
* <p>
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent);
@@ -240,7 +240,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
this.partitionMeta = deploy.getPartitionMeta();
this.tablespaceMeta = deploy.getTablespaceMeta();
this.storageEngine = deploy.getStorageEngine();
this.autoTunable = beanFinder == null && (entityType == EntityType.ORM || entityType == EntityType.VIEW);
this.autoTunable = entityType == EntityType.ORM || entityType == EntityType.VIEW;
// helper object used to derive lists of properties
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
@@ -1737,6 +1737,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
pc.clear(rootBeanType, idValue);
}
/**
* Clear a bean from the persistence context.
*/
public void contextClear(PersistenceContext pc) {
pc.clear(rootBeanType);
}
/**
* Delete a bean from the persistence context (such that we don't fetch it in the same transaction).
*/
@@ -315,7 +315,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
* Return the BeanDescriptors mapped to the table.
*/
public List<BeanDescriptor<?>> descriptors(String tableName) {
return tableToDescMap.get(tableName.toLowerCase());
return tableName == null ? Collections.emptyList() : tableToDescMap.get(tableName.toLowerCase());
}
/**
@@ -90,6 +90,10 @@ final class AnnotationFields extends AnnotationParser {
if (formula != null) {
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
DbComment comment = get(prop, DbComment.class);
if (comment != null) {
prop.setDbComment(comment.value());
}
initWhoProperties(prop);
initDbMigration(prop);
}
@@ -60,7 +60,7 @@ final class BetweenPropertyExpression extends NonPrepareExpression {
@Override
public void addSql(SpiExpressionRequest request) {
request.append(" ?").append(BETWEEN).property(name(lowProperty)).append(" and ").property(name(highProperty));
request.append("?").append(BETWEEN).property(name(lowProperty)).append(" and ").property(name(highProperty));
}
@Override
@@ -119,7 +119,11 @@ public final class Binder {
if (param.isEncryptionKey()) {
bindLog.append("****");
} else {
bindLog.append(value);
String sv = String.valueOf(value);
if (sv.length() > 50) {
sv = sv.substring(0, 47) + "...";
}
bindLog.append(sv);
}
}
if (value instanceof Collection) {
@@ -212,7 +212,7 @@ public final class DefaultPersister implements Persister {
if (bean._ebean_getIntercept().isUpdate()) {
update(bean, t);
} else {
insert(bean, t);
insert(bean, null, t);
}
}
@@ -220,12 +220,15 @@ public final class DefaultPersister implements Persister {
* Insert this bean.
*/
@Override
public void insert(EntityBean bean, Transaction t) {
public void insert(EntityBean bean, InsertOptions insertOptions, Transaction t) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.INSERT);
if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
if (insertOptions != null) {
req.setInsertOptions(insertOptions);
}
try {
req.initTransIfRequiredWithBatchCascade();
insert(req);
@@ -326,7 +326,7 @@ final class SaveManyBeans extends SaveManyBase {
}
private boolean isChangedProperty() {
return parentBean._ebean_getIntercept().isChangedProperty(many.propertyIndex());
return request.isChangedProperty(many.propertyIndex());
}
private void removeAssocManyOrphans() {
@@ -1,11 +1,15 @@
package io.ebeaninternal.server.persist.dml;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to support the generation of DML statements.
*/
public final class GenerateDmlRequest {
private final StringBuilder sb = new StringBuilder(100);
private final List<String> columns = new ArrayList<>();
private StringBuilder insertBindBuffer;
private String prefix;
private String prefix2;
@@ -26,7 +30,7 @@ public final class GenerateDmlRequest {
++bindColumnCount;
sb.append(prefix);
sb.append(column);
//sb.append(expr);
columns.add(column);
if (insertMode > 0) {
if (insertMode++ > 1) {
insertBindBuffer.append(',');
@@ -75,4 +79,8 @@ public final class GenerateDmlRequest {
public boolean isUpdate() {
return insertMode == 0;
}
public List<String> columns() {
return columns;
}
}
@@ -69,7 +69,7 @@ public final class InsertHandler extends DmlHandler {
SpiTransaction t = persistRequest.transaction();
// get the appropriate sql
sql = meta.getSql(withId);
sql = meta.sql(withId, persistRequest.insertOptions());
PreparedStatement pstmt;
if (persistRequest.isBatched()) {
pstmt = pstmtBatch(t, sql, persistRequest, useGeneratedKeys);
@@ -134,7 +134,8 @@ public final class InsertHandler extends DmlHandler {
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else {
} else if (persistRequest.insertOptions() == null) {
// insert on conflict do nothing can not return generated key
throw new PersistenceException("Autoincrement getGeneratedKeys() returned no rows?");
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebean.bean.EntityBean;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -30,15 +31,17 @@ final class InsertMeta {
private final Bindable shadowFKey;
private final String[] identityDbColumns;
private final Platform platform;
private final InsertMetaOptions options;
InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, BindableList all) {
this.platform = dbPlatform.platform();
this.options = InsertMetaPlatform.create(platform, desc, this);
this.id = id;
this.all = all;
this.shadowFKey = shadowFKey;
String tableName = desc.baseTable();
this.sqlWithId = genSql(false, tableName);
this.sqlWithId = sql(false, tableName);
// only available for single Id property
if (id.isConcatenated()) {
@@ -61,7 +64,7 @@ final class InsertMeta {
this.supportsGetGeneratedKeys = dbPlatform.dbIdentity().isSupportsGetGeneratedKeys();
this.supportsSelectLastInsertedId = desc.supportsSelectLastInsertedId();
}
this.sqlNullId = genSql(true, tableName);
this.sqlNullId = sql(true, tableName);
}
}
@@ -113,22 +116,31 @@ final class InsertMeta {
}
/**
* get the sql based whether the id value(s) are null.
* Return the sql for the given options.
*/
public String getSql(boolean withId) {
if (withId) {
return sqlWithId;
} else {
return sqlNullId;
public String sql(boolean withId, InsertOptions insertOptions) {
if (insertOptions == null) {
return sqlFor(withId);
}
return options.sql(withId, insertOptions);
}
private String genSql(boolean nullId, String table) {
String sqlFor(boolean withId) {
return withId ? sqlWithId : sqlNullId;
}
private String sql(boolean nullId, String table) {
GenerateDmlRequest request = new GenerateDmlRequest();
sql(request, nullId, table);
return request.toString();
}
void sql(GenerateDmlRequest request, boolean nullId, String table) {
request.setInsertSetMode();
request.append("insert into ").append(table);
if (nullId && noColumnsForInsert()) {
return request.append(defaultValues()).toString();
request.append(defaultValues());
return;
}
request.append(" (");
if (!nullId) {
@@ -142,7 +154,6 @@ final class InsertMeta {
request.append(") values (");
request.append(request.insertBindBuffer());
request.append(")");
return request.toString();
}
private String defaultValues() {
@@ -165,5 +176,4 @@ final class InsertMeta {
return shadowFKey == null
&& all.isEmpty();
}
}
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
/**
* Generator for insert SQL with options.
*/
interface InsertMetaOptions {
/**
* Generate the SQL for the given insert options.
*/
String sql(boolean withId, InsertOptions insertOptions);
}
@@ -0,0 +1,113 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Postgres specific generation of insert on conflict.
*/
final class InsertMetaOptionsPostgres implements InsertMetaOptions {
private final InsertMeta meta;
private final BeanDescriptor<?> desc;
private final String baseTable;
private final Map<String, String> sqlCache = new ConcurrentHashMap<>();
InsertMetaOptionsPostgres(InsertMeta meta, BeanDescriptor<?> desc) {
this.meta = meta;
this.desc = desc;
this.baseTable = desc.baseTable();
}
@Override
public String sql(boolean withId, InsertOptions options) {
String key = withId + options.key();
return sqlCache.computeIfAbsent(key, k -> generate(withId, options));
}
private String generate(boolean withId, InsertOptions options) {
char type = options.key().charAt(0);
switch (type) {
case 'U':
return generate(withId, false, options);
case 'N':
return generate(withId, true, options);
default:
return meta.sqlFor(withId);
}
}
private String generate(boolean withId, boolean doNothing, InsertOptions options) {
GenerateDmlRequest request = new GenerateDmlRequest();
meta.sql(request, !withId, baseTable);
request.append(" on conflict ");
List<String> uniqueColumns = desc.uniqueProps().stream()
.flatMap(Arrays::stream)
.map(BeanProperty::dbColumn)
.collect(Collectors.toList());
String constraintName = options.constraint();
if (constraintName != null) {
request.append("on constraint ").append(constraintName);
} else {
request.append("(");
String cols = options.uniqueColumns();
if (cols != null) {
request.append(cols);
} else {
appendUniqueColumns(uniqueColumns, request);
}
request.append(")");
}
if (doNothing) {
request.append(" do nothing");
return request.toString();
}
request.append(" do update set ");
String updateSet = options.updateSet();
if (updateSet != null) {
request.append(updateSet);
} else {
setColumns(withId, request, uniqueColumns);
}
return request.toString();
}
private void setColumns(boolean withId, GenerateDmlRequest request, List<String> uniqueColumns) {
List<String> columns = request.columns();
columns.removeAll(uniqueColumns);
if (withId) {
BeanProperty idProperty = desc.idProperty();
if (idProperty != null && !idProperty.isEmbedded()) {
columns.remove(idProperty.dbColumn());
}
}
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
request.append(", ");
}
String col = columns.get(i);
request.append(col).append("=excluded.").append(col);
}
}
private static void appendUniqueColumns(List<String> uniqueColumns, GenerateDmlRequest request) {
if (uniqueColumns.isEmpty()) {
throw new IllegalStateException("Unable to identify unique columns for INSERT ON CONFLICT - Add mapping like @Column(unique=true) or @Index(unique=true)");
}
for (int i = 0; i < uniqueColumns.size(); i++) {
if (i > 0) {
request.append(", ");
}
request.append(uniqueColumns.get(i));
}
}
}
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebeaninternal.server.deploy.BeanDescriptor;
final class InsertMetaPlatform {
private static final NotSupported NOT_SUPPORTED = new NotSupported();
static InsertMetaOptions create(Platform platform, BeanDescriptor<?> desc, InsertMeta meta) {
switch (platform.base()) {
case POSTGRES:
case YUGABYTE:
case COCKROACH:
return new InsertMetaOptionsPostgres(meta, desc);
default:
return NOT_SUPPORTED;
}
}
static final class NotSupported implements InsertMetaOptions {
@Override
public String sql(boolean withId, InsertOptions insertOptions) {
throw new UnsupportedOperationException("InsertOptions not supported on this database platform");
}
}
}
@@ -169,7 +169,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
this.queryMode = query.mode();
this.loadContextBean = queryMode.isLoadContextBean() || query.getForUpdateLockType() != null;
this.lazyLoadManyProperty = query.lazyLoadMany();
this.readOnly = request.isReadOnly();
this.readOnly = query.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.parentNode();
this.profilingListener = query.profilingListener();
@@ -74,6 +74,9 @@ public final class CQueryEngine {
if (request.logSql()) {
request.logSql("{0}; --bind({1}) --micros({2}) --rows({3})", query.generatedSql(), query.bindLog(), query.micros(), rows);
}
if (rows > 0) {
request.clearContext();
}
return rows;
} catch (SQLException e) {
throw translate(request, query.bindLog(), query.generatedSql(), e);
@@ -61,6 +61,7 @@ final class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery
/**
* Execute the update or delete statement returning the row count.
*/
@SuppressWarnings("resource")
public int execute() throws SQLException {
long startNano = System.nanoTime();
try {
@@ -109,6 +110,7 @@ final class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery
pstmt = null;
}
@SuppressWarnings("resource")
@Override
public void profile() {
transaction()
@@ -287,6 +287,11 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public <A> Optional<A> findSingleAttributeOrEmpty() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public boolean isCountDistinct() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -51,6 +51,10 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
private <T> void flushJdbcBatchOnQuery(OrmQueryRequest<T> request) {
if (request.query().isUsingFuture()) {
// future queries never invoke a flush
return;
}
SpiTransaction t = request.transaction();
if (t.isFlushOnQuery()) {
// before we perform a query, we need to flush any
@@ -9,8 +9,10 @@ import io.ebeaninternal.api.*;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import javax.annotation.Nullable;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -86,6 +88,12 @@ public final class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQue
return this;
}
@Override
public DtoQuery<T> usingConnection(Connection connection) {
this.transaction = new ExternalJdbcTransaction(connection);
return this;
}
@Override
public DtoQuery<T> usingMaster() {
this.useMaster = true;
@@ -57,6 +57,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private Type type;
private String label;
private Mode mode = Mode.NORMAL;
private boolean usingFuture;
private Object tenantId;
/**
* Holds query in structured form.
@@ -908,6 +909,16 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
this.mode = mode;
}
@Override
public final void usingFuture() {
this.usingFuture = true;
}
@Override
public final boolean isUsingFuture() {
return usingFuture;
}
@Override
public final boolean isUsageProfiling() {
return usageProfiling;
@@ -1092,7 +1103,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
// add the rawSql statement - if any
if (orderByIsEmpty()) {
if (rawSql != null && rawSql.getSql() != null) {
order(rawSql.getSql().getOrderBy());
orderBy(rawSql.getSql().getOrderBy());
}
}
if (checkPagingOrderBy()) {
@@ -1448,7 +1459,6 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
}
@Override
@SuppressWarnings("unchecked")
public final <A> List<A> findSingleAttributeList() {
return server.findSingleAttributeList(this);
}
@@ -1464,6 +1474,11 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return !list.isEmpty() ? list.get(0) : null;
}
@Override
public final <A> Optional<A> findSingleAttributeOrEmpty() {
return Optional.ofNullable(findSingleAttribute());
}
@Override
public final T findOne() {
return server.findOne(this);
@@ -7,7 +7,9 @@ import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import java.sql.Connection;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -50,6 +52,12 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
return this;
}
@Override
public SqlQuery usingConnection(Connection connection) {
this.transaction = new ExternalJdbcTransaction(connection);
return this;
}
@Override
public SqlQuery usingMaster() {
this.useMaster = true;
@@ -1,9 +1,12 @@
package io.ebeaninternal.server.type;
import io.ebean.core.type.ScalarType;
import io.ebean.core.type.BasicTypeConverter;
import io.ebean.core.type.ScalarType;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.util.UUID;
/**
@@ -32,6 +35,8 @@ interface ArrayElementConverter<T> {
ArrayElementConverter<Double> DOUBLE = new DoubleConverter();
ArrayElementConverter<Float> FLOAT = new FloatConverter();
ArrayElementConverter<BigDecimal> BIG_DECIMAL = new BigDecimalConverter();
ArrayElementConverter<Instant> INSTANT = new InstantConverter();
ArrayElementConverter<LocalDate> LOCAL_DATE = new LocalDateConverter();
class LongConverter implements ArrayElementConverter<Long> {
@@ -101,6 +106,48 @@ interface ArrayElementConverter<T> {
}
}
class InstantConverter implements ArrayElementConverter<Instant> {
@Override
public Instant fromSerialized(Object rawValue) {
return fromDbArray(rawValue);
}
@Override
public Instant fromDbArray(Object rawValue) {
if (rawValue instanceof Instant) {
return (Instant) rawValue;
} else if (rawValue instanceof Timestamp) {
return ((Timestamp) rawValue).toInstant();
} else if (rawValue instanceof String) {
return Instant.parse((String) rawValue);
} else {
throw new IllegalStateException("Instant fromDbArray unsupported for " + rawValue);
}
}
}
class LocalDateConverter implements ArrayElementConverter<LocalDate> {
@Override
public LocalDate fromSerialized(Object rawValue) {
return fromDbArray(rawValue);
}
@Override
public LocalDate fromDbArray(Object rawValue) {
if (rawValue instanceof LocalDate) {
return (LocalDate) rawValue;
} else if (rawValue instanceof java.sql.Date) {
return ((java.sql.Date) rawValue).toLocalDate();
} else if (rawValue instanceof String) {
return LocalDate.parse((String) rawValue);
} else {
throw new IllegalStateException("Instant fromDbArray unsupported for " + rawValue);
}
}
}
class BigDecimalConverter implements ArrayElementConverter<BigDecimal> {
@Override
@@ -141,7 +188,7 @@ interface ArrayElementConverter<T> {
@Override
public java.util.UUID fromSerialized(Object rawValue) {
return java.util.UUID.fromString((String)rawValue);
return java.util.UUID.fromString((String) rawValue);
}
@Override
@@ -16,6 +16,8 @@ import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
@@ -63,6 +65,12 @@ class ScalarTypeArrayList extends ScalarTypeArrayBase<List> implements ScalarTyp
if (valueType.equals(String.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING));
}
if (valueType.equals(Instant.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "timestamptz", DocPropertyType.TEXT, ArrayElementConverter.INSTANT));
}
if (valueType.equals(LocalDate.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "date", DocPropertyType.TEXT, ArrayElementConverter.LOCAL_DATE));
}
throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping");
} finally {
lock.unlock();
@@ -8,6 +8,8 @@ import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -58,6 +60,12 @@ final class ScalarTypeArrayListH2 extends ScalarTypeArrayList {
if (valueType.equals(String.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING));
}
if (valueType.equals(Instant.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "timestamp", DocPropertyType.TEXT, ArrayElementConverter.INSTANT));
}
if (valueType.equals(LocalDate.class)) {
return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "date", DocPropertyType.TEXT, ArrayElementConverter.LOCAL_DATE));
}
throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping");
} finally {
lock.unlock();
@@ -2,12 +2,13 @@ package io.ebeaninternal.server.util;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.List;
/**
* Stack based on ArrayList.
*/
public class ArrayStack<E> {
public class ArrayStack<E> implements Iterable<E> {
private final List<E> list;
@@ -89,4 +90,9 @@ public class ArrayStack<E> {
public boolean contains(E o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
}
@@ -15,7 +15,7 @@ public class BetweenPropertyExpressionTest extends BaseExpressionTest {
public void sqlExpression() {
TDSpiExpressionRequest request = new TDSpiExpressionRequest(null);
exp("a", "b", 10).addSql(request);
Assertions.assertThat(request.sql()).isEqualTo(" ? between a and b");
Assertions.assertThat(request.sql()).isEqualTo("? between a and b");
}
@Test
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
@@ -1,5 +1,6 @@
package io.ebeaninternal.dbmigration.model;
import io.ebean.annotation.ConstraintMode;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DdlHelp;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
@@ -22,6 +23,8 @@ public class MCompoundForeignKey {
private final List<String> columns = new ArrayList<>();
private final List<String> referenceColumns = new ArrayList<>();
private String indexName;
private ConstraintMode fkeyOnDelete;
private ConstraintMode fkeyOnUpdate;
public MCompoundForeignKey(String name, String referenceTable, String indexName) {
this.name = name;
@@ -47,9 +50,15 @@ public class MCompoundForeignKey {
fk.setColumnNames(toColumnNames(columns));
fk.setRefColumnNames(toColumnNames(referenceColumns));
fk.setRefTableName(referenceTable);
fk.setOnDelete(fkeyModeOf(fkeyOnDelete));
fk.setOnUpdate(fkeyModeOf(fkeyOnUpdate));
return fk;
}
private String fkeyModeOf(ConstraintMode mode) {
return (mode == null) ? null : mode.name();
}
/**
* Create and return an AlterForeignKey migration element.
*/
@@ -63,7 +72,7 @@ public class MCompoundForeignKey {
fk.setTableName(tableName);
return fk;
}
/**
* Create and return an AlterForeignKey migration element.
*/
@@ -117,7 +126,6 @@ public class MCompoundForeignKey {
* Return as an array of string column names.
*/
private String toColumnNames(List<String> columns) {
StringBuilder sb = new StringBuilder(40);
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
@@ -139,7 +147,7 @@ public class MCompoundForeignKey {
return true;
if (!(obj instanceof MCompoundForeignKey))
return false;
MCompoundForeignKey other = (MCompoundForeignKey) obj;
return Objects.equals(columns, other.columns)
&& Objects.equals(indexName, other.indexName)
@@ -148,4 +156,8 @@ public class MCompoundForeignKey {
&& Objects.equals(referenceTable, other.referenceTable);
}
public void setForeignKeyModes(ConstraintMode onDelete, ConstraintMode onUpdate) {
this.fkeyOnDelete = onDelete;
this.fkeyOnUpdate = onUpdate;
}
}
@@ -88,7 +88,6 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitEnd() {
// set the primary key name
table.setPkName(primaryKeyName());
@@ -153,6 +152,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitOneImported(BeanPropertyAssocOne<?> p) {
PropertyForeignKey foreignKey = p.foreignKey();
boolean addForeignKey = foreignKey == null || !foreignKey.isNoConstraint();
TableJoinColumn[] columns = p.tableJoin().columns();
if (columns.length == 0) {
@@ -162,17 +163,19 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
List<MColumn> modelColumns = new ArrayList<>(columns.length);
MCompoundForeignKey compoundKey = null;
if (columns.length > 1) {
if (addForeignKey && columns.length > 1) {
// compound foreign key
String refTable = p.targetDescriptor().baseTable();
String fkName = foreignKeyConstraintName(p.name());
String fkIndex = foreignKeyIndexName(p.name());
compoundKey = new MCompoundForeignKey(fkName, refTable, fkIndex);
if (foreignKey != null) {
compoundKey.setForeignKeyModes(foreignKey.getOnDelete(), foreignKey.getOnUpdate());
}
table.addForeignKey(compoundKey);
}
for (TableJoinColumn column : columns) {
String dbCol = column.getLocalDbColumn();
BeanProperty importedProperty = p.findMatchImport(dbCol);
if (importedProperty == null) {
@@ -184,26 +187,28 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
MColumn col = table.addColumn(dbCol, columnDefn, !p.isNullable());
col.setDbMigrationInfos(p.dbMigrationInfos());
col.setDefaultValue(p.dbColumnDefault());
if (columns.length == 1) {
if (p.hasForeignKeyConstraint() && !importedProperty.descriptor().suppressForeignKey()) {
// single references column (put it on the column)
String refTable = importedProperty.descriptor().baseTable();
if (refTable == null) {
// odd case where an EmbeddedId only has 1 property
refTable = p.targetDescriptor().baseTable();
}
col.setReferences(refTable + "." + refColumn);
col.setForeignKeyName(foreignKeyConstraintName(col.getName()));
if (p.hasForeignKeyIndex()) {
col.setForeignKeyIndex(foreignKeyIndexName(col.getName()));
}
PropertyForeignKey foreignKey = p.foreignKey();
if (foreignKey != null) {
col.setForeignKeyModes(foreignKey.getOnDelete(), foreignKey.getOnUpdate());
col.setComment(p.dbComment());
if (addForeignKey) {
if (columns.length > 1) {
compoundKey.addColumnPair(dbCol, refColumn);
} else {
if (p.hasForeignKeyConstraint() && !importedProperty.descriptor().suppressForeignKey()) {
// single references column (put it on the column)
String refTable = importedProperty.descriptor().baseTable();
if (refTable == null) {
// odd case where an EmbeddedId only has 1 property
refTable = p.targetDescriptor().baseTable();
}
col.setReferences(refTable + "." + refColumn);
col.setForeignKeyName(foreignKeyConstraintName(col.getName()));
if (p.hasForeignKeyIndex()) {
col.setForeignKeyIndex(foreignKeyIndexName(col.getName()));
}
if (foreignKey != null) {
col.setForeignKeyModes(foreignKey.getOnDelete(), foreignKey.getOnUpdate());
}
}
}
} else {
compoundKey.addColumnPair(dbCol, refColumn);
}
modelColumns.add(col);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebean.DatabaseBuilder;
import io.localtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
@@ -11,11 +9,13 @@ import io.ebean.platform.h2.H2Platform;
import io.ebean.platform.sqlserver.SqlServer17Platform;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DefaultConstraintMaxLength;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.dbmigration.model.ModelContainer;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.localtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.CKeyAssoc;
import org.tests.model.basic.CKeyDetail;
@@ -44,6 +44,8 @@ public class ModelBuildBeanVisitorTest extends BaseTestCase {
new VisitAllUsing(addTable, defaultServer).visitAllBeans();
assert_compound_pk(model);
assert_compound_fk(model);
assert_comment_onManyToOne(model);
}
@Test
@@ -69,7 +71,7 @@ public class ModelBuildBeanVisitorTest extends BaseTestCase {
config.setDbOffline(true);
config.setDatabasePlatform(new SqlServer17Platform());
final SpiEbeanServer database = (SpiEbeanServer)DatabaseFactory.create(config);
final SpiEbeanServer database = (SpiEbeanServer) DatabaseFactory.create(config);
try {
ModelBuildContext ctx = new ModelBuildContext(model, config.getDatabasePlatform(), config.getConstraintNaming(), true);
@@ -101,4 +103,25 @@ public class ModelBuildBeanVisitorTest extends BaseTestCase {
assertThat(item.primaryKeyColumns()).hasSize(2);
}
private void assert_compound_fk(ModelContainer model) {
MTable item = model.getTable("item");
assertThat(item).isNotNull();
List<MCompoundForeignKey> compoundKeys = item.getCompoundKeys();
assertThat(compoundKeys).hasSize(1);
MCompoundForeignKey compoundForeignKey = compoundKeys.get(0);
ForeignKey foreignKey = compoundForeignKey.createForeignKey();
assertThat(foreignKey.getName()).isEqualTo("fk_item_etype");
assertThat(foreignKey.getColumnNames()).isEqualTo("customer,type");
assertThat(foreignKey.getRefTableName()).isEqualTo("\"type\"");
assertThat(foreignKey.getOnDelete()).isEqualTo("SET_NULL");
assertThat(foreignKey.getOnUpdate()).isEqualTo("SET_DEFAULT");
}
private void assert_comment_onManyToOne(ModelContainer model) {
MTable message = model.getTable("c_message");
MColumn conversation = message.getColumn("conversation_id");
assertThat(conversation.getType()).isEqualTo("bigint");
assertThat(conversation.getComment()).isEqualTo("A ManyToOne comment");
}
}
@@ -1,5 +1,7 @@
package org.tests.model.compositekeys;
import io.ebean.annotation.ConstraintMode;
import io.ebean.annotation.DbForeignKey;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
@@ -32,11 +34,13 @@ public class Item {
@Version
private Long version;
@DbForeignKey(onDelete = ConstraintMode.SET_NULL, onUpdate = ConstraintMode.SET_DEFAULT)
@ManyToOne
@JoinColumn(name = "customer", referencedColumnName = "customer", insertable = false, updatable = false)
@JoinColumn(name = "type", referencedColumnName = "type", insertable = false, updatable = false)
private Type eType;
@DbForeignKey(noConstraint = true)
@ManyToOne
@JoinColumn(name = "customer", referencedColumnName = "customer", insertable = false, updatable = false)
@JoinColumn(name = "region", referencedColumnName = "type", insertable = false, updatable = false)
@@ -1,5 +1,6 @@
package org.tests.model.converstation;
import io.ebean.annotation.DbComment;
import org.tests.model.BaseModel;
import jakarta.persistence.Entity;
@@ -14,6 +15,7 @@ public class Message extends BaseModel {
String body;
@DbComment("A ManyToOne comment")
@ManyToOne
Conversation conversation;
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,7 +14,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
@@ -63,14 +63,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
@@ -84,7 +84,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
@@ -304,6 +304,14 @@ public abstract class PBaseComparable<R, T> extends PBaseValueEqual<R, T> {
return _root;
}
/**
* Between - value between this property and another property
*/
public final R betweenProperties(Query.Property<T> highProperty, T value) {
expr().betweenProperties(_name, highProperty.toString(), value);
return _root;
}
/**
* Greater than.
*
@@ -32,7 +32,7 @@ public abstract class PBaseString<R, T> extends PBaseComparable<R, T> {
* @param value the equal to bind value
* @return the root query bean instance
*/
@Deprecated
@Deprecated(forRemoval = true)
public final R equalToType(T value) {
expr().eq(_name, value);
return _root;
@@ -46,7 +46,7 @@ public abstract class PBaseString<R, T> extends PBaseComparable<R, T> {
* @param value the equal to bind value
* @return the root query bean instance
*/
@Deprecated
@Deprecated(forRemoval = true)
public final R notEqualToType(T value) {
expr().ne(_name, value);
return _root;
@@ -665,7 +665,7 @@ public abstract class TQRootBean<T, R> {
* Add EXISTS sub-query predicate.
*/
public R exists(Query<?> subQuery) {
query.where().exists(subQuery);
peekExprList().exists(subQuery);
return root;
}
@@ -673,7 +673,7 @@ public abstract class TQRootBean<T, R> {
* Add NOT EXISTS sub-query predicate.
*/
public R notExists(Query<?> subQuery) {
query.where().notExists(subQuery);
peekExprList().notExists(subQuery);
return root;
}
@@ -684,7 +684,7 @@ public abstract class TQRootBean<T, R> {
* @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values.
*/
public final R exists(String sqlSubQuery, Object... bindValues) {
query().where().exists(sqlSubQuery, bindValues);
peekExprList().exists(sqlSubQuery, bindValues);
return root;
}
@@ -695,7 +695,7 @@ public abstract class TQRootBean<T, R> {
* @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values.
*/
public final R notExists(String sqlSubQuery, Object... bindValues) {
query().where().notExists(sqlSubQuery, bindValues);
peekExprList().notExists(sqlSubQuery, bindValues);
return root;
}
@@ -1171,7 +1171,7 @@ public abstract class TQRootBean<T, R> {
/**
* @deprecated migrate to {@link #orderBy()}.
*/
@Deprecated(since = "13.19")
@Deprecated(since = "13.19", forRemoval = true)
public R order() {
return root;
}
@@ -1191,7 +1191,7 @@ public abstract class TQRootBean<T, R> {
/**
* @deprecated migrate to {@link #orderBy(String)}
*/
@Deprecated(since = "13.19")
@Deprecated(since = "13.19", forRemoval = true)
public R order(String orderByClause) {
return orderBy(orderByClause);
}
@@ -1644,6 +1644,28 @@ public abstract class TQRootBean<T, R> {
return query.findSingleAttribute();
}
/**
* Execute the query returning a single optional attribute value.
* <p>
* <h3>Example</h3>
* <pre>{@code
*
* Optional<String> maybeName =
* new QCustomer()
* .select(name)
* .id.eq(42)
* .status.eq(NEW)
* .findSingleAttributeOrEmpty();
*
* }</pre>
*
* @return an optional value for the selected property
*/
@Nullable
public <A> Optional<A> findSingleAttributeOrEmpty() {
return query.findSingleAttributeOrEmpty();
}
/**
* Execute the query processing the beans one at a time.
* <p>
@@ -1,8 +1,9 @@
package org.example.domain;
import io.ebean.annotation.DbArray;
import jakarta.persistence.*;
import org.example.domain.finder.ContactFinder;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.ZonedDateTime;
@@ -17,6 +18,11 @@ import java.util.Map;
@Table(name = "be_contact")
public class Contact extends BaseModel {
/**
* Convenience Finder for 'active record' style.
*/
public static final ContactFinder find = new ContactFinder();
@DbArray
List<@Size(max=20) String> phoneNumbers = new ArrayList<>();
@@ -0,0 +1,12 @@
package org.example.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class PFile {
@Id
long id;
String name;
}
@@ -0,0 +1,12 @@
package org.example.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class PLong {
@Id
long id;
String name;
}
@@ -0,0 +1,12 @@
package org.example.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class Pinstant {
@Id
long id;
String name;
}
@@ -0,0 +1,18 @@
package org.example.domain.finder;
import io.ebean.Finder;
import org.example.domain.Contact;
import org.example.domain.query.QContact;
/**
*/
public class ContactFinder extends Finder<Long, Contact> {
public ContactFinder() {
super(Contact.class);
}
public QContact typed() {
return new QContact();
}
}
@@ -1,6 +1,8 @@
package org.querytest;
import org.example.domain.Contact;
import org.example.domain.Customer;
import org.example.domain.query.QCustomer;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
@@ -9,6 +11,8 @@ import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class QCustomerAndOrTest {
@Test
@@ -55,6 +59,30 @@ public class QCustomerAndOrTest {
}
@Test
public void testOrWithExists() {
QCustomer query = Customer.find.typed()
.alias("_cust")
.or()
.name.eq("Superman")
.exists(Contact.find.typed()
.alias("contact")
.firstName.eq("Superman")
.raw("contact.customer_id = _cust.id")
.query()
)
.endOr()
.select(QCustomer.alias().id);
query.findList();
assertThat(query.getGeneratedSql()).isEqualTo(
"select _cust.id from be_customer _cust where (" +
"_cust.name = ? or exists (select 1 from be_contact contact where " +
"contact.first_name = ? and contact.customer_id = _cust.id))"
);
}
private Date fiveDaysAgo() {
LocalDateTime fiveDaysAgo = LocalDate.now().atStartOfDay().minusDays(5);
return new Date(fiveDaysAgo.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
@@ -63,6 +63,25 @@ public class QCustomerTest {
}
@Test
public void copy() {
var origin = new QCustomer()
.setDistinct(true)
.status.equalTo(Customer.Status.BAD);
var copy1 = origin.copy().name.isNotNull();
var q1 = copy1.query();
copy1.findList();
assertThat(q1.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.name is not null");
var copy2 = origin.copy().version.ge(1L);
var q2 = copy2.query();
copy2.findList();
assertThat(q2.getGeneratedSql()).contains("from be_customer t0 where t0.status = ? and t0.version >= ?");
}
@Test
public void findSingleAttribute() {
@@ -813,6 +832,30 @@ public class QCustomerTest {
new QContact()
.firstName.inRangeWith(lastName, "B")
.findList();
new QContact()
.firstName.between("A", "B")
.findList();
}
@Test
void betweenProperties() {
var query = new QContact()
.firstName.betweenProperties(lastName, "B");
query.findList();
assertThat(query.getGeneratedSql()).contains(" where ? between t0.first_name and t0.last_name");
}
@Test
void betweenProperties_notFirstPredicate() {
var query = new QContact()
.lastName.isNotNull()
.firstName.betweenProperties(lastName, "B")
.email.isNotNull();
query.findList();
assertThat(query.getGeneratedSql()).contains(" where t0.last_name is not null and ? between t0.first_name and t0.last_name and t0.email is not null");
}
@Test
@@ -1026,6 +1069,32 @@ public class QCustomerTest {
assertThat(maxDate).isNotNull();
}
@Test
public void findSingleAttributeOrEmpty() {
Customer cust = new Customer();
cust.setName("MaybeIExist yeah");
cust.setStatus(Customer.Status.GOOD);
cust.setRegistered(new Date());
cust.save();
Optional<String> customerName = new QCustomer()
.select(name)
.status.eq(Customer.Status.GOOD)
.name.startsWith("MaybeIExist")
.findSingleAttributeOrEmpty();
assertThat(customerName).isPresent();
Optional<String> customerName2 = new QCustomer()
.select(name)
.status.eq(Customer.Status.GOOD)
.name.eq("NahIDoNotExist")
.findSingleAttributeOrEmpty();
assertThat(customerName2).isEmpty();
}
@Test
public void testFetchByScalarValue() {
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -22,28 +22,28 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
@@ -57,7 +57,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
+9 -9
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</parent>
<name>ebean test</name>
@@ -12,8 +12,8 @@
<artifactId>ebean-test</artifactId>
<properties>
<bytebuddy.version>1.14.9</bytebuddy.version>
<assertj.version>3.24.2</assertj.version>
<bytebuddy.version>1.14.12</bytebuddy.version>
<assertj.version>3.25.3</assertj.version>
</properties>
<dependencies>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.25.0-jakarta</version>
<version>14.0.0</version>
<scope>test</scope>
</dependency>
@@ -293,7 +293,7 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.3</version>
<version>1.4</version>
<scope>provided</scope>
</dependency>
@@ -1,5 +1,6 @@
package io.ebean.xtest.base;
import io.ebean.Transaction;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import org.junit.jupiter.api.AfterEach;
@@ -18,15 +19,17 @@ import org.tests.model.basic.ResetBasicData;
*/
public abstract class TransactionalTestCase extends BaseTestCase {
private Transaction transaction;
@BeforeEach
public void startTransaction() {
ResetBasicData.reset();
DB.beginTransaction();
transaction = DB.beginTransaction();
}
@AfterEach
public void endTransaction() {
DB.rollbackTransaction();
DB.endTransaction();
transaction.rollback();
transaction.end();
}
}
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.ArrayList;
import java.util.Comparator;
@@ -13,22 +14,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanListTest {
private final Object object1 = new Object();
private final Object object2 = new Object();
private final Object object3 = new Object();
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private List<Object> all() {
List<Object> all = new ArrayList<>();
all.add(object1);
all.add(object2);
all.add(object3);
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
return all;
}
private List<Object> some() {
List<Object> some = new ArrayList<>();
some.add(object2);
some.add(object3);
some.add(new Product(2));
some.add(new Product(3));
return some;
}
@@ -163,14 +164,15 @@ public class BeanListTest {
@Test
public void testRemove_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.remove(object2);
list.remove(object3);
list.remove(all.get(1));
list.remove(all.get(2));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -179,13 +181,14 @@ public class BeanListTest {
@Test
public void testRemoveAll_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
list.addAll(all);
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.removeAll(some());
list.removeAll(List.of(all.get(1), all.get(2)));
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -3,7 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.EBasic;
import org.tests.model.basic.Product;
import java.util.*;
@@ -14,24 +14,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class BeanMapTest {
private final EBasic object1 = new EBasic("o1");
private final EBasic object2 = new EBasic("o2");
private final EBasic object3 = new EBasic("o3");
private final EBasic object4 = new EBasic("o4");
private final EBasic object5 = new EBasic("o5");
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private final Product object4 = new Product(4);
private final Product object5 = new Product(5);
private Map<String, Object> all() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("1", object1);
all.put("2", object2);
all.put("3", object3);
all.put("1", new Product(1));
all.put("2", new Product(2));
all.put("3", new Product(3));
return all;
}
private Map<String, Object> some() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("2", object2);
all.put("3", object3);
all.put("2", new Product(2));
all.put("3", new Product(3));
return all;
}
@@ -73,8 +73,8 @@ public class BeanMapTest {
@Test
public void testAdd_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
@@ -90,11 +90,13 @@ public class BeanMapTest {
@Test
public void testAddSome_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
map.putAll(all());
Map<String, Object> all = Map.of("1", object1, "2", object2, "3", object3);
map.putAll(all);
assertThat(map.modifyAdditions()).containsOnly(object1);
assertThat(map.modifyRemovals()).isEmpty();
@@ -179,7 +181,7 @@ public class BeanMapTest {
@Test
public void testClear_given_someBeansInAdditions() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
map.put("2", object2);
map.put("3", object3);
@@ -193,7 +195,7 @@ public class BeanMapTest {
@Test
public void keySet_add_whenModifyListening() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().add("3"));
}
@@ -205,7 +207,7 @@ public class BeanMapTest {
@Test
public void keySet_addAll_whenModifyListening() {
BeanMap<String, EBasic> map = newModifyListeningMap();
BeanMap<String, Product> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().addAll(asList("3", "4")));
}
@@ -317,13 +319,13 @@ public class BeanMapTest {
@Test
public void values_add() {
BeanMap<String, EBasic> map = new BeanMap<>();
BeanMap<String, Product> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().add(object3));
}
@Test
public void values_addAll() {
BeanMap<String, EBasic> map = new BeanMap<>();
BeanMap<String, Product> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().addAll(asList(object3, object5)));
}
@@ -337,8 +339,8 @@ public class BeanMapTest {
@Test
public void entrySet_clear() {
final BeanMap<String, EBasic> map = newModifyListeningMap();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.clear();
assertThat(entries).isEmpty();
@@ -348,8 +350,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -365,8 +367,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove_whenNotEqualValue() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -379,11 +381,11 @@ public class BeanMapTest {
@Test
public void entrySet_iterator_remove() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final Iterator<Map.Entry<String, EBasic>> iterator = entries.iterator();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final Iterator<Map.Entry<String, Product>> iterator = entries.iterator();
while (iterator.hasNext()) {
final Map.Entry<String, EBasic> entry = iterator.next();
final Map.Entry<String, Product> entry = iterator.next();
if (entry.getKey().equals("2") || entry.getKey().equals("5")) {
iterator.remove();
}
@@ -395,8 +397,8 @@ public class BeanMapTest {
@Test
public void entrySet_removeAll() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(3);
@@ -406,8 +408,8 @@ public class BeanMapTest {
@Test
public void entrySet_retainAll() {
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(2);
@@ -415,15 +417,15 @@ public class BeanMapTest {
assertThat(map.modifyRemovals()).containsOnly(object2, object3, object5);
}
private BeanMap<String, EBasic> newModifyListeningMap() {
BeanMap<String, EBasic> map = new BeanMap<>();
private BeanMap<String, Product> newModifyListeningMap() {
BeanMap<String, Product> map = new BeanMap<>();
map.put("1", object1);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return map;
}
private BeanMap<String, EBasic> newModifyListeningMap5() {
BeanMap<String, EBasic> map = new BeanMap<>();
private BeanMap<String, Product> newModifyListeningMap5() {
BeanMap<String, Product> map = new BeanMap<>();
map.put("1", object1);
map.put("2", object2);
map.put("3", object3);
@@ -3,6 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -12,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanSetTest {
Object object1 = new Object();
Object object2 = new Object();
Object object3 = new Object();
Product object1 = new Product(1);
Product object2 = new Product(2);
Product object3 = new Product(3);
private Set<Object> all() {
Set<Object> all = new LinkedHashSet<>();
all.add(object1);
all.add(object2);
all.add(object3);
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
return all;
}
private Set<Object> some() {
Set<Object> some = new LinkedHashSet<>();
some.add(object2);
some.add(object3);
some.add(new Product(2));
some.add(new Product(3));
return some;
}
@@ -99,7 +100,9 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
@@ -115,11 +118,13 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
set.removeAll(some());
set.removeAll(Set.of(object2, object3));
assertThat(set.modifyAdditions()).containsOnly(object1);
assertThat(set.modifyRemovals()).isEmpty();
@@ -5,28 +5,19 @@ import org.junit.jupiter.api.Test;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ContainerConfigTest {
class ContainerConfigTest {
@Test
public void loadFromProperties() {
Properties p = new Properties();
p.setProperty("ebean.cluster.active", "true");
p.setProperty("ebean.cluster.serviceName", "a");
p.setProperty("ebean.cluster.namespace", "b");
p.setProperty("ebean.cluster.podName", "c");
p.setProperty("ebean.cluster.port", "42");
void loadFromProperties() {
ContainerConfig containerConfig = new ContainerConfig();
containerConfig.loadFromProperties(p);
assertEquals(true, containerConfig.isActive());
assertEquals("a", containerConfig.getServiceName());
assertEquals("b", containerConfig.getNamespace());
assertEquals("c", containerConfig.getPodName());
assertEquals(42, containerConfig.getPort());
assertThat(containerConfig.isActive()).isFalse();
assertThat(containerConfig.getServiceName()).isNull();
assertThat(containerConfig.getNamespace()).isNull();
assertThat(containerConfig.getPodName()).isNull();
assertThat(containerConfig.getPort()).isEqualTo(0);
}
}

Some files were not shown because too many files have changed in this diff Show More