Compare commits

..
Author SHA1 Message Date
Robin Bygrave 2cc4c2c265 [maven-release-plugin] prepare release ebean-8.2.1 2016-08-05 13:53:03 +12:00
Robin Bygrave 0a830841c4 Bump pom to 8.2.1-SNAPSHOT 2016-08-05 13:52:09 +12:00
Robin Bygrave c427009b93 #812 - Remove deprecated includeSoftDeletes() ... migrate to setIncludeSoftDeletes() 2016-08-05 13:47:32 +12:00
Robin Bygrave 7f42d045ac No effective change - javadoc typo 2016-08-05 13:29:28 +12:00
Robin Bygrave 43afaf1344 #811 - ENH: Restore findIterate() as alternative to findEach / findEachWhile 2016-08-05 13:01:42 +12:00
Robin Bygrave 6c182ecaa5 #810 - ENH: Add transaction.isRollbackOnly() 2016-08-05 12:14:58 +12:00
Robin Bygrave fdb839f90e #809 - Add test / example showing ... transaction.setPersistCascade(false) and transaction.commitAndContinue(); 2016-08-05 11:20:12 +12:00
Robin Bygrave 16be213faf #808 - ENH: Add Transaction commitAndContinue() ... 2016-08-05 10:39:17 +12:00
Robin Bygrave 3344f0ddb0 Refactor - remove unused method JdbcTransaction.notifyQueryOnly(). 2016-08-04 23:25:50 +12:00
Robin Bygrave 479ac38537 #807 - Deprecate TransactionEventListener ... migrate to TransactionCallback 2016-08-04 23:17:57 +12:00
Robin Bygrave e30ad80f5f #806 - Deprecate OnQueryOnly.CLOSE ... 2016-08-04 23:02:26 +12:00
Robin Bygrave 08e46d6bf3 #805 - ENH: Add BeanState.resetForInsert() ... This resets bean state such that a save() results in an insert 2016-08-04 22:49:07 +12:00
Robin Bygrave 876476fdc1 #804 - Remove API - findMap(property, type) ... migrate to query.setMapKey() 2016-08-04 20:30:18 +12:00
Robin Bygrave 81de83a6e7 #803 - Fix generics on findMap() to ... <K> Map<K, T> findMap(); 2016-08-04 20:23:11 +12:00
Robin Bygrave b5b221ffb1 No effective change - remove unused constructors 2016-08-04 17:32:18 +12:00
Robin Bygrave 3c6eab6c85 #802 - Find generics on findIds() - refactor internals of find ids to use find single attribute 2016-08-04 17:28:41 +12:00
Robin Bygrave 1be30f1671 #125 - ENH: Add support for query returning a List of single Attribute type 2016-08-04 16:41:50 +12:00
Robin Bygrave 9a5cb242ce No effective change - remove commented out line from pom 2016-08-04 13:56:09 +12:00
Robin Bygrave ea69cae4a3 [maven-release-plugin] prepare for next development iteration 2016-08-03 16:24:43 +12:00
74 changed files with 1348 additions and 917 deletions
+2 -3
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebean</groupId>
<artifactId>ebean</artifactId>
<version>8.1.1</version>
<version>8.2.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -37,7 +37,7 @@
<scm>
<developerConnection>scm:git:https://github.com/ebean-orm/avaje-ebeanorm.git</developerConnection>
<tag>ebean-8.1.1</tag>
<tag>ebean-8.2.1</tag>
</scm>
<dependencies>
@@ -256,7 +256,6 @@
<phase>process-test-classes</phase>
<configuration>
<classSource>target/test-classes</classSource>
<!--<packages>com.avaje.tests.**</packages>-->
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
@@ -120,4 +120,9 @@ public interface BeanState {
* for a fully loaded entity bean.
*/
void setLoaded();
/**
* Reset the bean putting it into NEW state such that a save() results in an insert.
*/
void resetForInsert();
}
+56 -3
View File
@@ -757,9 +757,28 @@ public interface EbeanServer {
/**
* Return the Id values of the query as a List.
*
* @see com.avaje.ebean.Query#findIds()
* @see Query#findIds()
*/
<T> List<Object> findIds(Query<T> query, Transaction transaction);
<A> List<A> findIds(Query<?> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* <p>
* Generally using {@link #findEach(Query, QueryEachConsumer, Transaction)} or
* {@link #findEachWhile(Query, QueryEachWhileConsumer, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* </p>
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
* </p>
*
* @see Query#findIterate()
* @see Query#findEach(QueryEachConsumer)
* @see Query#findEachWhile(QueryEachWhileConsumer)
*/
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
@@ -986,7 +1005,41 @@ public interface EbeanServer {
* @return the map of fetched beans.
* @see Query#findMap()
*/
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning a list of values for a single property.
*
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
*
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
*
* @see Query#findSingleAttributeList()
*/
<A> List<A> findSingleAttributeList(Query<?> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean or null (if no matching
@@ -31,7 +31,7 @@ import java.util.Set;
* more methods than you would initially expect (the ones duplicated from
* Query).
* </p>
*
*
* @see Query#where()
*/
public interface ExpressionList<T> {
@@ -83,14 +83,14 @@ public interface ExpressionList<T> {
/**
* Add an orderBy clause to the query.
*
*
* @see Query#orderBy(String)
*/
Query<T> orderBy(String orderBy);
/**
* Add an orderBy clause to the query.
*
*
* @see Query#orderBy(String)
*/
Query<T> setOrderBy(String orderBy);
@@ -116,12 +116,6 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Deprecated in favour of setIncludeSoftDeletes().
*/
@Deprecated
Query<T> includeSoftDeletes();
/**
* Execute the query including soft deleted rows.
*/
@@ -147,6 +141,13 @@ public interface ExpressionList<T> {
*/
int update();
/**
* Execute the query iterating over the results.
*
* @see Query#findIterate()
*/
QueryIterator<T> findIterate();
/**
* Execute the query process the beans one at a time.
*
@@ -164,17 +165,17 @@ public interface ExpressionList<T> {
/**
* Execute the query returning a list.
*
*
* @see Query#findList()
*/
List<T> findList();
/**
* Execute the query returning the list of Id's.
*
*
* @see Query#findIds()
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Return the count of entities this query should return.
@@ -193,22 +194,49 @@ public interface ExpressionList<T> {
/**
* Execute the query returning a set.
*
*
* @see Query#findSet()
*/
Set<T> findSet();
/**
* Execute the query returning a map.
*
*
* @see Query#findMap()
*/
Map<?, T> findMap();
<K> Map<K, T> findMap();
/**
* Return a typed map specifying the key property and type.
* Execute the query returning a list of values for a single property.
*
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
*
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
*/
<K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
<A> List<A> findSingleAttributeList();
/**
* Execute the query returning a single bean or null (if no matching
@@ -232,7 +260,7 @@ public interface ExpressionList<T> {
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
*
* @return a Future object for the row count query
*/
FutureRowCount<T> findFutureCount();
@@ -251,7 +279,7 @@ public interface ExpressionList<T> {
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
*
* @return a Future object for the list of Id's
*/
FutureIds<T> findFutureIds();
@@ -263,7 +291,7 @@ public interface ExpressionList<T> {
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
*
* @return a Future object for the list result of the query
*/
FutureList<T> findFutureList();
@@ -326,7 +354,7 @@ public interface ExpressionList<T> {
/**
* Specify specific properties to fetch on the main/root bean (aka partial
* object).
*
*
* @see Query#select(String)
*/
Query<T> select(String properties);
@@ -351,28 +379,28 @@ public interface ExpressionList<T> {
/**
* Set the first row to fetch.
*
*
* @see Query#setFirstRow(int)
*/
Query<T> setFirstRow(int firstRow);
/**
* Set the maximum number of rows to fetch.
*
*
* @see Query#setMaxRows(int)
*/
Query<T> setMaxRows(int maxRows);
/**
* Set the name of the property which values become the key of a map.
*
*
* @see Query#setMapKey(String)
*/
Query<T> setMapKey(String mapKey);
/**
* Set to true to use the query for executing this query.
*
*
* @see Query#setUseCache(boolean)
*/
Query<T> setUseCache(boolean useCache);
@@ -633,7 +661,7 @@ public interface ExpressionList<T> {
* To get control over the options you can create an ExampleExpression and set
* those options such as case insensitive etc.
* </p>
*
*
* <pre>{@code
*
* // create an example bean and set the properties
@@ -641,26 +669,26 @@ public interface ExpressionList<T> {
* Customer example = new Customer();
* example.setName("Rob%");
* example.setNotes("%something%");
*
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).where()
* // pass the bean into the where() clause
* .exampleLike(example)
* // you can add other expressions to the same query
* .gt("id", 2).findList();
*
*
* }</pre>
*
*
* Similarly you can create an ExampleExpression
*
*
* <pre>{@code
*
* Customer example = new Customer();
* example.setName("Rob%");
* example.setNotes("%something%");
*
*
* // create a ExampleExpression with more control
* ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO).includeZeros();
*
*
* List<Customer> list = Ebean.find(Customer.class).where().add(qbe).findList();
*
* }</pre>
@@ -762,7 +790,7 @@ public interface ExpressionList<T> {
* Exists expression
*/
ExpressionList<T> exists(Query<?> subQuery);
/**
* Not exists expression
*/
@@ -789,7 +817,7 @@ public interface ExpressionList<T> {
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
*
* @param propertyMap
* a map keyed by property names.
*/
@@ -9,8 +9,6 @@ import java.util.concurrent.Future;
* It extends the java.util.concurrent.Future with the ability to get the Id's
* while the query is still executing in the background.
* </p>
*
* @author rbygrave
*/
public interface FutureIds<T> extends Future<List<Object>> {
@@ -19,16 +17,4 @@ public interface FutureIds<T> extends Future<List<Object>> {
*/
Query<T> getQuery();
/**
* Return the list of Id's which could be partially populated.
* <p>
* That is the query getting the id's could still be running and adding id's
* to this list.
* </p>
* <p>
* To get the list of Id's ensuring the query has finished use the
* {@link Future#get()} method instead of this one.
* </p>
*/
List<Object> getPartialIds();
}
+3 -13
View File
@@ -495,7 +495,7 @@ public abstract class Model {
/**
* Return typically a different EbeanServer to the default.
* <p>
* This is equivilent to {@link Ebean#getServer(String)}
* This is equivalent to {@link Ebean#getServer(String)}
*
* @param server
* The name of the EbeanServer. If this is null then the default EbeanServer is
@@ -598,7 +598,7 @@ public abstract class Model {
* <p>
* Equivalent to {@link Query#findIds()}
*/
public List<Object> findIds() {
public <A> List<A> findIds() {
return query().findIds();
}
@@ -660,20 +660,10 @@ public abstract class Model {
* <p>
* Equivalent to {@link Query#findMap()}
*/
public Map<?, T> findMap() {
public <K> Map<K, T> findMap() {
return query().findMap();
}
/**
* Executes the query and returns the results as a map of the objects specifying the map key
* property.
* <p>
* Equivalent to {@link Query#findMap(String, Class)}
*/
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
return query().findMap(keyProperty, keyType);
}
/**
* Executes a find row count query in a background thread.
* <p>
+82 -5
View File
@@ -542,7 +542,47 @@ public interface Query<T> {
*
* @see EbeanServer#findIds(Query, Transaction)
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Execute the query iterating over the results.
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results (typically in a finally block).
* </p>
* <p>
* findEach() and findEachWhile() are preferred to findIterate() as they ensure
* the jdbc statement and resultSet are closed at the end of the iteration.
* </p>
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
* <pre>{@code
*
* Query<Customer> query =
* ebeanServer.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
*/
QueryIterator<T> findIterate();
/**
* Execute the query processing the beans one at a time.
@@ -552,6 +592,11 @@ public interface Query<T> {
* (unlike #findList #findSet etc)
* </p>
* <p>
* Note that findEach (and findEachWhile and findIterate) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
@@ -588,6 +633,11 @@ public interface Query<T> {
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
* <p>
* Note that findEachWhile (and findEach and findIterate) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
* with Java8 closures.
@@ -661,7 +711,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* Map<?, Product> map =
* Map<String, Product> map =
* ebeanServer.find(Product.class)
* .setMapKey("sku")
* .findMap();
@@ -670,12 +720,39 @@ public interface Query<T> {
*
* @see EbeanServer#findMap(Query, Transaction)
*/
Map<?, T> findMap();
<K> Map<K, T> findMap();
/**
* Return a typed map specifying the key property and type.
* Execute the query returning a list of values for a single property.
*
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
*
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
*/
<K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
<A> List<A> findSingleAttributeList();
/**
* Execute the query returning either a single bean or null (if no matching
@@ -0,0 +1,67 @@
package com.avaje.ebean;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results (typically in a finally block).
* </p>
*
* <pre>{@code
*
* Query<Customer> query = server.find(Customer.class)
* .where().gt("id", 0)
* .orderBy("id")
* .setMaxRows(2);
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
*
* @param <T>
* the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*/
T next();
/**
* Remove is not allowed.
*/
void remove();
/**
* Close the underlying resources held by this iterator.
*/
void close();
}
+46 -3
View File
@@ -54,13 +54,52 @@ public interface Transaction extends Closeable {
*/
void setReadOnly(boolean readOnly);
/**
* Commits the transaction at this point with the expectation that another
* commit (or rollback or end) will occur later to complete the transaction.
* <p>
* This is similar to commit() but leaves the transaction "Active".
* </p>
* <h3>Functions/h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Perform post-commit processing updating L2 cache, ElasticSearch etc</li>
* </ul>
*/
void commitAndContinue() throws RollbackException;
/**
* Commit the transaction.
* <p>
* This performs commit and completes the transaction closing underlying resources and
* marking the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Perform post-commit processing updating L2 cache, ElasticSearch etc</li>
* <li>Close any underlying resources, closing the underlying JDBC connection</li>
* <li>Mark the transaction as "Inactive"</li>
* </ul>
*/
void commit() throws RollbackException;
/**
* Rollback the transaction.
* <p>
* This performs rollback, closes underlying resources and marks the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <ul>
* <li>Call rollback on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Close any underlying resources, closing the underlying JDBC connection</li>
* <li>Mark the transaction as "Inactive"</li>
* </ul>
*/
void rollback() throws PersistenceException;
@@ -79,6 +118,11 @@ public interface Transaction extends Closeable {
*/
void setRollbackOnly();
/**
* Return true if the transaction is marked as rollback only.
*/
boolean isRollbackOnly();
/**
* If the transaction is active then perform rollback. Otherwise do nothing.
*/
@@ -422,9 +466,8 @@ public interface Transaction extends Closeable {
/**
* Add an arbitrary user object to the transaction. The objects added have no
* impact on any internals of ebena and are solely meant as a convenient
* method push user information to e.g. the
* {@link com.avaje.ebean.event.TransactionEventListener}.
* impact on any internals of ebean and are solely meant as a convenient
* method push user information (although somewhat replaced by TransactionCallback).
*/
void putUserObject(String name, Object value);
@@ -342,6 +342,13 @@ public final class EntityBeanIntercept implements Serializable {
return state == STATE_LOADED;
}
/**
* Set the bean into NEW state.
*/
public void setNew() {
this.state = STATE_NEW;
}
/**
* Set the loaded state to true.
* <p>
@@ -14,7 +14,6 @@ import com.avaje.ebean.event.BeanPostLoad;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BulkTableEventListener;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
@@ -331,7 +330,6 @@ public class ServerConfig {
private List<BeanQueryAdapter> queryAdapters = new ArrayList<BeanQueryAdapter>();
private List<BulkTableEventListener> bulkTableEventListeners = new ArrayList<BulkTableEventListener>();
private List<ServerConfigStartup> configStartupListeners = new ArrayList<ServerConfigStartup>();
private List<TransactionEventListener> transactionEventListeners = new ArrayList<TransactionEventListener>();
/**
* By default inserts are included in the change log.
@@ -2052,35 +2050,6 @@ public class ServerConfig {
this.persistControllers = persistControllers;
}
/**
* Register a TransactionEventListener instance
* <p>
* Note alternatively you can use {@link #setTransactionEventListeners(List)}
* to set all the TransactionEventListener instances.
* </p>
*/
public void add(TransactionEventListener listener) {
transactionEventListeners.add(listener);
}
/**
* Return the TransactionEventListener instances.
*/
public List<TransactionEventListener> getTransactionEventListeners() {
return transactionEventListeners;
}
/**
* Register all the TransactionEventListener instances.
* <p>
* Note alternatively you can use {@link #add(TransactionEventListener)} to
* add TransactionEventListener instances one at a time.
* </p>
*/
public void setTransactionEventListeners(List<TransactionEventListener> transactionEventListeners) {
this.transactionEventListeners = transactionEventListeners;
}
/**
* Register a BeanPersistListener instance.
* <p>
@@ -33,12 +33,6 @@ public class DatabasePlatform {
*/
ROLLBACK,
/**
* Just close the transaction. Valid at READ_COMMITTED isolation and preferred on some Databases
* as a performance optimisation.
*/
CLOSE,
/**
* Commit the transaction
*/
@@ -1,18 +0,0 @@
package com.avaje.ebean.event;
import com.avaje.ebean.Transaction;
/**
* Used to get notified about commit or rollback of a transaction
*/
public interface TransactionEventListener {
/**
* Called after the transaction has been committed
*/
void postTransactionCommit(Transaction tx);
/**
* Called after the transaction has been rolled back
*/
void postTransactionRollback(Transaction tx, Throwable cause);
}
@@ -1,18 +0,0 @@
package com.avaje.ebean.event;
import com.avaje.ebean.Transaction;
/**
* A no operation implementation of TransactionEventListener. Objects extending
* this need to only override the methods they want to.
*/
public abstract class TransactionEventListenerAdapter implements TransactionEventListener {
public void postTransactionCommit(Transaction tx) {
// do nothing by default
}
public void postTransactionRollback(Transaction tx, Throwable cause) {
// do nothing by default
}
}
@@ -29,9 +29,13 @@ public class ScopedTransaction implements SpiTransaction {
public ScopedTransaction(ScopeTrans scopeTrans) {
this.scopeTrans = scopeTrans;
this.transaction =scopeTrans.getTransaction();
this.transaction = scopeTrans.getTransaction();
}
@Override
public void commitAndContinue() throws RollbackException {
transaction.commitAndContinue();
}
@Override
public void commit() throws RollbackException {
@@ -49,11 +53,21 @@ public class ScopedTransaction implements SpiTransaction {
scopeTrans.rollback(e);
}
@Override
public void rollbackIfActive() {
transaction.rollbackIfActive();
}
@Override
public void setRollbackOnly() {
scopeTrans.setRollbackOnly();
}
@Override
public boolean isRollbackOnly() {
return transaction.isRollbackOnly();
}
@Override
public void end() throws PersistenceException {
try {
@@ -145,7 +145,7 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* the query has finished (if executing in a background thread).
* </p>
*/
<T> List<Object> findIdsWithCopy(Query<T> query, Transaction t);
<A> List<A> findIdsWithCopy(Query<?> query, Transaction t);
/**
* Execute the findRowCount query but without copying the query.
@@ -78,6 +78,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
ID_LIST,
/**
* Find single attribute.
*/
ATTRIBUTE,
/**
* Find rowCount.
*/
@@ -246,21 +251,6 @@ public interface SpiQuery<T> extends Query<T> {
List<String> getSoftDeletePredicates();
/**
* Set the list of Id's that is being populated.
* <p>
* This is a mutating list of id's and we are setting this so that other
* threads have access to the id's before the id query has finished.
* </p>
*/
void setIdList(List<Object> ids);
/**
* Return the list of Id's that is currently being fetched by a background
* thread.
*/
List<Object> getIdList();
/**
* Return a copy of the query.
*/
@@ -341,6 +331,24 @@ public interface SpiQuery<T> extends Query<T> {
*/
void setSelectId();
/**
* Mark the query as selecting a single attribute.
*/
void setSingleAttribute();
/**
* Return true if this is singleAttribute query.
*/
boolean isSingleAttribute();
/**
* Return true if the query should include the Id property.
* <p>
* distinct and single attribute queries exclude the Id property.
* </p>
*/
boolean isWithId();
/**
* Set a filter to a join path.
*/
@@ -220,6 +220,12 @@ public interface SpiTransaction extends Transaction {
*/
Connection getInternalConnection();
/**
* Rollback if the transaction is active. This provides an internal
* mechanism for rollback failures occur on commit().
*/
void rollbackIfActive();
/**
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
@@ -68,11 +68,11 @@ public abstract class BeanRequest {
public void rollbackTransIfRequired() {
if (createdTransaction) {
try {
transaction.rollback();
transaction.rollbackIfActive();
} catch (Exception e) {
// Just log this and carry on. A previous exception has been
// thrown and if this rollback throws exception it likely means
// that the connection is broken (and the datasource and db will cleanup)
// that the connection is broken (and the dataSource and db will cleanup)
log.error("Error trying to rollback a transaction (after a prior exception thrown)", e);
}
}
@@ -84,4 +84,9 @@ public class DefaultBeanState implements BeanState {
public boolean isDisableLazyLoad() {
return intercept.isDisableLazyLoad();
}
@Override
public void resetForInsert() {
intercept.setNew();
}
}
@@ -195,7 +195,6 @@ public class DefaultContainer implements SpiContainer {
bootup.addPersistControllers(serverConfig.getPersistControllers());
bootup.addPostLoaders(serverConfig.getPostLoaders());
bootup.addFindControllers(serverConfig.getFindControllers());
bootup.addTransactionEventListeners(serverConfig.getTransactionEventListeners());
bootup.addPersistListeners(serverConfig.getPersistListeners());
bootup.addQueryAdapters(serverConfig.getQueryAdapters());
bootup.addServerConfigStartup(serverConfig.getServerConfigStartupListeners());
@@ -71,10 +71,8 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -1134,18 +1132,36 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> Map<?, T> findMap(Query<T> query, Transaction t) {
public <K, T> Map<K, T> findMap(Query<T> query, Transaction t) {
SpiOrmQueryRequest request = createQueryRequest(Type.MAP, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
return (Map<?, T>) result;
return (Map<K, T>) result;
}
try {
request.initTransIfRequired();
return (Map<?, T>) request.findMap();
return (Map<K, T>) request.findMap();
} finally {
request.endTransIfRequired();
}
}
@Override
@SuppressWarnings("unchecked")
public <A> List<A> findSingleAttributeList(Query<?> query, Transaction t) {
SpiOrmQueryRequest request = createQueryRequest(Type.ATTRIBUTE, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
return (List<A>) result;
}
try {
request.initTransIfRequired();
return (List<A>) request.findSingleAttributeList();
} finally {
request.endTransIfRequired();
@@ -1174,16 +1190,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> List<Object> findIds(Query<T> query, Transaction t) {
public <A> List<A> findIds(Query<?> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
return findIdsWithCopy(copy, t);
return findIdsWithCopy(((SpiQuery<?>) query).copy(), t);
}
public <T> List<Object> findIdsWithCopy(Query<T> query, Transaction t) {
public <A> List<A> findIdsWithCopy(Query<?> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ID_LIST, query, t);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query, t);
try {
request.initTransIfRequired();
return request.findIds();
@@ -1241,12 +1255,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
copy.setFutureFetch(true);
// this is the list we will put the id's in ... create it now so
// it is available for other threads to read while the id query
// is still executing (we don't need to wait for it to finish)
List<Object> idList = Collections.synchronizedList(new ArrayList<Object>());
copy.setIdList(idList);
Transaction newTxn = createTransaction();
CallableQueryIds<T> call = new CallableQueryIds<T>(this, copy, newTxn);
@@ -1294,6 +1302,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new LimitOffsetPagedList<T>(this, spiQuery);
}
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
try {
request.initTransIfRequired();
return request.findIterate();
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
}
}
public <T> void findEach(Query<T> query, QueryEachConsumer<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
@@ -340,14 +340,14 @@ public class InternalConfiguration {
boolean localL2 = cacheManager.isLocalL2Caching();
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
return new ExplicitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
}
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
return new AutoCommitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
}
return new TransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager, this.getBootupClasses());
return new TransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
}
/**
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.api.BeanIdList;
import java.util.List;
@@ -21,6 +21,14 @@ public interface OrmQueryEngine {
*/
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
/**
* Execute the findSingleAttributeList query.
*/
<A> List<A> findSingleAttributeList(OrmQueryRequest<?> request);
/**
* Execute the findVersions query.
*/
<T> List<Version<T>> findVersions(OrmQueryRequest<T> request);
/**
@@ -36,7 +44,7 @@ public interface OrmQueryEngine {
/**
* Execute the find id's query.
*/
<T> BeanIdList findIds(OrmQueryRequest<T> request);
<A> List<A> findIds(OrmQueryRequest<?> request);
/**
* Execute the query as a delete statement.
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.RawSql;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
@@ -12,7 +13,6 @@ import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.text.json.JsonReadOptions;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.CQueryPlanKey;
import com.avaje.ebeaninternal.api.HashQuery;
import com.avaje.ebeaninternal.api.LoadContext;
@@ -304,9 +304,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.findRowCount(this);
}
public List<Object> findIds() {
BeanIdList idList = queryEngine.findIds(this);
return idList.getIdList();
public <A> List<A> findIds() {
return queryEngine.findIds(this);
}
public void findEach(QueryEachConsumer<T> consumer) {
@@ -373,6 +372,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return (Map<?, ?>) queryEngine.findMany(this);
}
/**
* Execute the findSingleAttributeList query.
*/
@Override
public <A> List<A> findSingleAttributeList() {
return queryEngine.findSingleAttributeList(this);
}
/**
* Return a bean specific finder if one has been set.
*/
@@ -821,7 +821,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Add the bean to the TransactionEvent. This will be used by TransactionManager to synch Cache,
* Add the bean to the TransactionEvent. This will be used by TransactionManager to sync Cache,
* Cluster and text indexes.
*/
private void addEvent() {
@@ -1,60 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
*
* <pre class="code">
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .fetch(&quot;contacts&quot;, new FetchConfig().query(2))
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
*
* QueryIterator&lt;Customer&gt; it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer...
* }
* } finally {
* // close the associated resources
* it.close();
* }
* </pre>
*
* @author rbygrave
*
* @param <T>
* the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*/
T next();
/**
* Remove is not allowed.
*/
void remove();
/**
* Close the underlying resources held by this iterator.
*/
void close();
}
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -70,7 +71,7 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* Execute the find ids query.
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Execute the find returning a QueryIterator and visitor pattern.
@@ -107,6 +108,11 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
*/
Map<?, ?> findMap();
/**
* Execute the findSingleAttributeList query.
*/
<A> List<A> findSingleAttributeList();
/**
* Try to get the query result from the query cache.
*/
@@ -41,7 +41,7 @@ final class TransWrapper {
void rollbackIfCreated() {
if (wasCreated){
transaction.rollback();
transaction.rollbackIfActive();
}
}
@@ -10,7 +10,6 @@ import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanPostLoad;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
@@ -54,8 +53,6 @@ public class BootupClasses implements ClassFilter {
private final List<Class<?>> beanPostLoadList = new ArrayList<Class<?>>();
private final List<Class<?>> transactionEventListenerList = new ArrayList<Class<?>>();
private final List<Class<?>> beanFindControllerList = new ArrayList<Class<?>>();
private final List<Class<?>> beanQueryAdapterList = new ArrayList<Class<?>>();
@@ -70,7 +67,6 @@ public class BootupClasses implements ClassFilter {
private final List<BeanPostLoad> beanPostLoadInstances = new ArrayList<BeanPostLoad>();
private final List<BeanPersistListener> persistListenerInstances = new ArrayList<BeanPersistListener>();
private final List<BeanQueryAdapter> queryAdapterInstances = new ArrayList<BeanQueryAdapter>();
private final List<TransactionEventListener> transactionEventListenerInstances = new ArrayList<TransactionEventListener>();
private Class<?> changeLogPrepareClass;
private Class<?> changeLogListenerClass;
@@ -181,19 +177,6 @@ public class BootupClasses implements ClassFilter {
}
}
/**
* Add TransactionEventListeners instances.
*/
public void addTransactionEventListeners(List<TransactionEventListener> transactionEventListeners) {
if (transactionEventListeners != null) {
for (TransactionEventListener c : transactionEventListeners) {
this.transactionEventListenerInstances.add(c);
// don't automatically instantiate
this.transactionEventListenerList.remove(c.getClass());
}
}
}
public void addPersistListeners(List<BeanPersistListener> listenerInstances) {
if (listenerInstances != null) {
for (BeanPersistListener l : listenerInstances) {
@@ -351,14 +334,6 @@ public class BootupClasses implements ClassFilter {
return idGeneratorInstances;
}
public List<TransactionEventListener> getTransactionEventListeners() {
// add class registered TransactionEventListener to the already created instances
for (Class<?> cls : transactionEventListenerList) {
createAdd(cls, transactionEventListenerInstances);
}
return transactionEventListenerInstances;
}
/**
* Return the list of Embeddable classes.
*/
@@ -440,11 +415,6 @@ public class BootupClasses implements ClassFilter {
interesting = true;
}
if (TransactionEventListener.class.isAssignableFrom(cls)) {
transactionEventListenerList.add(cls);
interesting = true;
}
if (ScalarType.class.isAssignableFrom(cls)) {
scalarTypeList.add(cls);
interesting = true;
@@ -253,11 +253,6 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.asDraft();
}
@Override
public Query<T> includeSoftDeletes() {
return setIncludeSoftDeletes();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return query.setIncludeSoftDeletes();
@@ -354,10 +349,15 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public List<Object> findIds() {
public <A> List<A> findIds() {
return query.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return query.findIterate();
}
@Override
public void findEach(QueryEachConsumer<T> consumer) {
query.findEach(consumer);
@@ -379,13 +379,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public Map<?, T> findMap() {
public <K> Map<K, T> findMap() {
return query.findMap();
}
@Override
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
return query.findMap(keyProperty, keyType);
public <A> List<A> findSingleAttributeList() {
return query.findSingleAttributeList();
}
@Override
@@ -64,7 +64,7 @@ public class FilterExpressionList<T> extends DefaultExpressionList<T> {
}
@Override
public Map<?, T> findMap() {
public <K> Map<K, T> findMap() {
return rootQuery.findMap();
}
@@ -12,6 +12,7 @@ import com.avaje.ebean.PagedList;
import com.avaje.ebean.Query;
import com.avaje.ebean.QueryEachConsumer;
import com.avaje.ebean.QueryEachWhileConsumer;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.search.Match;
@@ -62,6 +63,7 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
* This is expected to only used after expressions are built via query language parsing.
* </p>
*/
@SuppressWarnings("unchecked")
public void simplify() {
exprList.simplifyEntries();
@@ -325,11 +327,6 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.asDraft();
}
@Override
public Query<T> includeSoftDeletes() {
return setIncludeSoftDeletes();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return exprList.setIncludeSoftDeletes();
@@ -371,10 +368,15 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
}
@Override
public List<Object> findIds() {
public <A> List<A> findIds() {
return exprList.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return exprList.findIterate();
}
@Override
public void findEach(QueryEachConsumer<T> consumer) {
exprList.findEach(consumer);
@@ -391,13 +393,13 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
}
@Override
public Map<?, T> findMap() {
public <K> Map<K, T> findMap() {
return exprList.findMap();
}
@Override
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
return exprList.findMap(keyProperty, keyType);
public <A> List<A> findSingleAttributeList() {
return exprList.findSingleAttributeList();
}
@Override
@@ -1,6 +1,6 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
@@ -165,22 +165,16 @@ public class CQueryBuilder {
return StringHelper.replaceString(sql, "${RTA}", replaceWith);
}
/**
* Build the row count query.
*/
public <T> CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest<T> request) {
public CQueryFetchSingleAttribute buildFetchAttributeQuery(OrmQueryRequest<?> request) {
SpiQuery<T> query = request.getQuery();
query.setSelectId();
SpiQuery<?> query = request.getQuery();
query.setSingleAttribute();
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
String sql = queryPlan.getSql();
return new CQueryFetchIds(request, predicates, sql);
return new CQueryFetchSingleAttribute(request, predicates, queryPlan);
}
// use RawSql or generated Sql
@@ -188,13 +182,19 @@ public class CQueryBuilder {
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
String sql = s.getSql();
// cache the query plan
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryFetchIds(request, predicates, sql);
return new CQueryFetchSingleAttribute(request, predicates, queryPlan);
}
/**
* Build the find ids query.
*/
public <T> CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest<T> request) {
request.getQuery().setSelectId();
return buildFetchAttributeQuery(request);
}
/**
@@ -450,8 +450,8 @@ public class CQueryBuilder {
}
sb.append(select.getSelectSql());
if (query.isDistinctQuery() && dbOrderBy != null) {
// add the orderby columns to the select clause (due to distinct)
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
// add the orderBy columns to the select clause (due to distinct)
sb.append(", ").append(convertDbOrderByForSelect(dbOrderBy));
}
}
@@ -1,17 +1,16 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.core.DiffHelp;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.Binder;
@@ -89,29 +88,24 @@ public class CQueryEngine {
}
/**
* Build and execute the find Id's query.
* Build and execute the findSingleAttributeList query.
*/
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request);
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
return findAttributeList(request, rcQuery);
}
@SuppressWarnings("unchecked")
private <A> List<A> findAttributeList(OrmQueryRequest<?> request, CQueryFetchSingleAttribute rcQuery) {
try {
BeanIdList list = rcQuery.findIds();
List<A> list = (List<A>)rcQuery.findList();
if (request.isLogSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog());
}
if (request.isLogSummary()) {
request.getTransaction().logSummary(rcQuery.getSummary());
}
if (request.getQuery().isFutureFetch()) {
// end the transaction for futureFindIds (it had it's own one)
logger.debug("Future findIds completed!");
request.getTransaction().end();
}
return list;
} catch (SQLException e) {
@@ -119,6 +113,15 @@ public class CQueryEngine {
}
}
/**
* Build and execute the find Id's query.
*/
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
return findAttributeList(request, rcQuery);
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
String logSql = sql;
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
@@ -1,278 +0,0 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.type.DataReader;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Executes the select row count query.
*/
public class CQueryFetchIds {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchIds.class);
/**
* The overall find request wrapper object.
*/
private final OrmQueryRequest<?> request;
private final BeanDescriptor<?> desc;
private final SpiQuery<?> query;
/**
* Where clause predicates.
*/
private final CQueryPredicates predicates;
/**
* The final sql that is generated.
*/
private final String sql;
private RsetDataReader dataReader;
/**
* The statement used to create the resultSet.
*/
private PreparedStatement pstmt;
private String bindLog;
private int executionTimeMicros;
private int rowCount;
private final int maxRows;
/**
* Create the Sql select based on the request.
*/
public CQueryFetchIds(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
this.request = request;
this.query = request.getQuery();
this.sql = sql;
this.maxRows = query.getMaxRows();
query.setGeneratedSql(sql);
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
}
/**
* Return a summary description of this query.
*/
public String getSummary() {
StringBuilder sb = new StringBuilder(80);
sb.append("FindIds exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
.append("] type[").append(desc.getName())
.append("] predicates[").append(predicates.getLogWhereSql())
.append("] bind[").append(bindLog).append("]");
return sb.toString();
}
/**
* Return the bind log.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return the generated sql.
*/
public String getGeneratedSql() {
return sql;
}
/**
* Execute the query returning the row count.
*/
public BeanIdList findIds() throws SQLException {
long startNano = System.nanoTime();
try {
// get the list that we are going to put the id's into.
// This was already set so that it is available to be
// read by other threads (it is a synchronised list)
List<Object> idList = query.getIdList();
if (idList == null) {
// running in foreground thread (not FutureIds query)
idList = Collections.synchronizedList(new ArrayList<Object>());
query.setIdList(idList);
}
BeanIdList result = new BeanIdList(idList);
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
ResultSet rset = pstmt.executeQuery();
dataReader = new RsetDataReader(request.getDataTimeZone(), rset);
boolean hitMaxRows = false;
boolean hasMoreRows = false;
rowCount = 0;
DbReadContext ctx = new DbContext();
while (rset.next()) {
Object idValue = desc.getIdBinder().read(ctx);
idList.add(idValue);
// reset back to 0
dataReader.resetColumnPosition();
rowCount++;
if (maxRows > 0 && rowCount == maxRows) {
hitMaxRows = true;
hasMoreRows = rset.next();
break;
}
}
if (hitMaxRows) {
result.setHasMore(hasMoreRows);
}
long exeNano = System.nanoTime() - startNano;
executionTimeMicros = (int) exeNano / 1000;
return result;
} finally {
close();
}
}
/**
* Close the resources.
* <p>
* The jdbc resultSet and statement need to be closed. Its important that
* this method is called.
* </p>
*/
private void close() {
try {
if (dataReader != null) {
dataReader.close();
dataReader = null;
}
} catch (SQLException e) {
logger.error("Error closing DataReader", e);
}
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
}
class DbContext implements DbReadContext {
public void propagateState(Object e) {
throw new RuntimeException("Not Called");
}
public Mode getQueryMode() {
return Mode.NORMAL;
}
public DataReader getDataReader() {
return dataReader;
}
public Boolean isReadOnly() {
return Boolean.FALSE;
}
@Override
public boolean isDisableLazyLoading() {
return false;
}
public boolean isRawSql() {
return false;
}
public void register(String path, EntityBeanIntercept ebi) {
}
public void register(String path, BeanCollection<?> bc) {
}
public BeanPropertyAssocMany<?> getManyProperty() {
// always null
return null;
}
public PersistenceContext getPersistenceContext() {
// always null
return null;
}
public boolean isAutoTuneProfiling() {
return false;
}
public void profileBean(EntityBeanIntercept ebi, String prefix) {
// no-op
}
public void setCurrentPrefix(String currentPrefix, Map<String, String> pathMap) {
// no-op
}
public void setLazyLoadedChildBean(EntityBean loadedBean, Object lazyLoadParentId) {
// no-op
}
@Override
public boolean isDraftQuery() {
return false;
}
}
}
@@ -0,0 +1,172 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import com.avaje.ebeaninternal.server.type.ScalarType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Base compiled query request for single attribute queries.
*/
class CQueryFetchSingleAttribute {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
/**
* The overall find request wrapper object.
*/
private final OrmQueryRequest<?> request;
private final BeanDescriptor<?> desc;
private final SpiQuery<?> query;
/**
* Where clause predicates.
*/
private final CQueryPredicates predicates;
/**
* The final sql that is generated.
*/
private final String sql;
private RsetDataReader dataReader;
/**
* The statement used to create the resultSet.
*/
private PreparedStatement pstmt;
private String bindLog;
private int executionTimeMicros;
private int rowCount;
private final ScalarType<Object> scalarType;
/**
* Create the Sql select based on the request.
*/
public CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan plan) {
this.request = request;
this.query = request.getQuery();
this.sql = plan.getSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
this.scalarType = plan.getSingleProperty().getScalarType();
query.setGeneratedSql(sql);
}
/**
* Return a summary description of this query.
*/
protected String getSummary() {
StringBuilder sb = new StringBuilder(80);
sb.append("FindAttr exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
.append("] type[").append(desc.getName())
.append("] predicates[").append(predicates.getLogWhereSql())
.append("] bind[").append(bindLog).append("]");
return sb.toString();
}
/**
* Execute the query returning the row count.
*/
protected List<Object> findList() throws SQLException {
long startNano = System.nanoTime();
try {
prepareExecute();
List<Object> result = new ArrayList<Object>();
while (dataReader.next()) {
result.add(scalarType.read(dataReader));
dataReader.resetColumnPosition();
rowCount++;
}
long exeNano = System.nanoTime() - startNano;
executionTimeMicros = (int) exeNano / 1000;
return result;
} finally {
close();
}
}
/**
* Return the bind log.
*/
protected String getBindLog() {
return bindLog;
}
/**
* Return the generated sql.
*/
protected String getGeneratedSql() {
return sql;
}
private void prepareExecute() throws SQLException {
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery());
}
/**
* Close the resources.
* <p>
* The jdbc resultSet and statement need to be closed. Its important that
* this method is called.
* </p>
*/
private void close() {
try {
if (dataReader != null) {
dataReader.close();
dataReader = null;
}
} catch (SQLException e) {
logger.error("Error closing DataReader", e);
}
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
}
}
@@ -4,7 +4,7 @@ import java.sql.SQLException;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
@@ -5,7 +5,7 @@ import java.util.ArrayList;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
/**
@@ -266,4 +266,7 @@ public class CQueryPlan {
return stats.getLastQueryTime();
}
public BeanProperty getSingleProperty() {
return sqlTree.getRootNode().getSingleProperty();
}
}
@@ -1,11 +1,10 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryEngine;
@@ -65,12 +64,18 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
return queryEngine.findRowCount(request);
}
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
flushJdbcBatchOnQuery(request);
return queryEngine.findIds(request);
}
@Override
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
flushJdbcBatchOnQuery(request);
return queryEngine.findSingleAttributeList(request);
}
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
// LIMITATION: You can not use QueryIterator to load bean cache
@@ -1,12 +1,12 @@
package com.avaje.ebeaninternal.server.query;
import java.util.List;
import java.util.concurrent.FutureTask;
import com.avaje.ebean.FutureIds;
import com.avaje.ebean.Query;
import com.avaje.ebean.Transaction;
import java.util.List;
import java.util.concurrent.FutureTask;
/**
* Default implementation of FutureIds.
*/
@@ -31,10 +31,6 @@ public class QueryFutureIds<T> extends BaseFuture<List<Object>> implements Futur
return call.query;
}
public List<Object> getPartialIds() {
return call.query.getIdList();
}
public boolean cancel(boolean mayInterruptIfRunning) {
call.query.cancel();
return super.cancel(mayInterruptIfRunning);
@@ -260,7 +260,7 @@ public class SqlTreeBuilder {
// Optional many property for lazy loading query
BeanPropertyAssocMany<?> lazyLoadMany = (query == null) ? null : query.getLazyLoadMany();
boolean withId = !rawNoId && !subQuery && (query == null || !query.isDistinct());
boolean withId = !rawNoId && !subQuery && (query == null || query.isWithId());
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, SpiQuery.TemporalMode.of(query), disableLazyLoad);
} else if (prop instanceof BeanPropertyAssocMany<?>) {
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
@@ -62,4 +63,10 @@ public interface SqlTreeNode {
* Return true if the query has a many join.
*/
boolean hasMany();
/**
* Return the property for singleAttribute query.
*/
BeanProperty getSingleProperty();
}
@@ -83,13 +83,6 @@ public class SqlTreeNodeBean implements SqlTreeNode {
*/
private boolean intersectionAsOfTableAlias;
/**
* Construct for Raw SQL.
*/
public SqlTreeNodeBean(BeanDescriptor<?> desc, SqlTreeProperties props, boolean withId, boolean disableLazyLoad) {
this(null, null, desc, props, null, withId, null, null, disableLazyLoad);
}
/**
* Construct for leaf node.
*/
@@ -137,6 +130,11 @@ public class SqlTreeNodeBean implements SqlTreeNode {
pathMap = createPathMap(prefix, desc);
}
@Override
public BeanProperty getSingleProperty() {
return properties[0];
}
private Map<String, String> createPathMap(String prefix, BeanDescriptor<?> desc) {
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
@@ -55,6 +56,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
// nothing to do here
}
@Override
public BeanProperty getSingleProperty() {
throw new IllegalStateException("No expected");
}
/**
* Return true if the extra join is a many join.
* <p>
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
@@ -39,6 +40,11 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
this.parentPrefix = split[0];
}
@Override
public BeanProperty getSingleProperty() {
throw new IllegalStateException("No expected");
}
@Override
public void addAsOfTableAlias(SpiQuery<?> query) {
// do nothing here ...
@@ -25,14 +25,6 @@ public final class SqlTreeNodeRoot extends SqlTreeNodeBean {
this.includeJoin = includeJoin;
}
/**
* Construct for raw sql named queries.
*/
public SqlTreeNodeRoot(BeanDescriptor<?> desc, SqlTreeProperties props, boolean withId) {
super(desc, props, withId, false);
this.includeJoin = null;
}
/**
* Set AsOf support (at root level).
*/
@@ -123,8 +123,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private ReadEvent futureFetchAudit;
private List<Object> partialIds;
private int timeout;
/**
@@ -192,6 +190,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private boolean forUpdate;
private boolean singleAttribute;
/**
* Set to true if this query has been tuned by autoTune.
*/
@@ -534,6 +534,26 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
select(beanDescriptor.getIdBinder().getIdProperty());
}
@Override
public void setSingleAttribute() {
this.singleAttribute = true;
}
/**
* Return true if this is a single attribute query.
*/
public boolean isSingleAttribute() {
return singleAttribute;
}
/**
* Return true if the Id should be included in the query.
*/
@Override
public boolean isWithId() {
return !distinct && !singleAttribute;
}
@Override
public NaturalKeyBindParam getNaturalKeyBindParam() {
NaturalKeyBindParam namedBind = null;
@@ -1094,6 +1114,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
server.findEach(this, consumer, null);
}
@Override
public QueryIterator<T> findIterate() {
return server.findIterate(this, null);
}
@Override
public List<Version<T>> findVersions() {
this.temporalMode = TemporalMode.VERSIONS;
@@ -1122,15 +1147,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public Map<?, T> findMap() {
public <K> Map<K, T> findMap() {
return server.findMap(this, null);
}
@Override
@SuppressWarnings("unchecked")
public <K> Map<K, T> findMap(String keyProperty, Class<K> keyType) {
setMapKey(keyProperty);
return (Map<K, T>) findMap();
public <A> List<A> findSingleAttributeList() {
return (List<A>)server.findSingleAttributeList(this, null);
}
@Override
@@ -1482,16 +1506,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return disableReadAudit;
}
@Override
public List<Object> getIdList() {
return partialIds;
}
@Override
public void setIdList(List<Object> partialIds) {
this.partialIds = partialIds;
}
@Override
public boolean isFutureFetch() {
return futureFetch;
@@ -3,10 +3,9 @@ package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.bootup.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import java.sql.Connection;
@@ -18,9 +17,9 @@ import java.sql.Connection;
public class AutoCommitTransactionManager extends TransactionManager {
public AutoCommitTransactionManager(boolean localL2Caching, ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr) {
super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses);
super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr);
}
/**
@@ -4,10 +4,9 @@ import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.bootup.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -18,9 +17,9 @@ import java.sql.Connection;
public class ExplicitTransactionManager extends TransactionManager {
public ExplicitTransactionManager(boolean localL2Caching, ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr) {
super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr, bootupClasses);
super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr);
}
/**
@@ -44,11 +43,7 @@ public class ExplicitTransactionManager extends TransactionManager {
return DatabasePlatform.OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
}
if (DatabasePlatform.OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) {
// Not using OnQueryOnly.CLOSE with ExplicitJdbcTransaction
return DatabasePlatform.OnQueryOnly.COMMIT;
}
// default to commit if not defined on the platform
return dbPlatformOnQueryOnly == null ? DatabasePlatform.OnQueryOnly.COMMIT : dbPlatformOnQueryOnly;
// default to rollback if not defined on the platform
return dbPlatformOnQueryOnly == null ? DatabasePlatform.OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly;
}
}
@@ -853,33 +853,15 @@ public class JdbcTransaction implements SpiTransaction {
}
}
protected void notifyQueryOnly() {
if (manager != null) {
manager.notifyOfQueryOnly(this);
}
}
/**
* Rollback, Commit or Close for query only transaction.
* <p>
* For a transaction that was used for queries only we can choose to either
* rollback or just close the connection for performance.
* </p>
* Rollback or Commit for query only transaction.
*/
protected void connectionEndForQueryOnly() {
try {
switch (onQueryOnly) {
case ROLLBACK:
performRollback();
break;
case COMMIT:
performCommit();
break;
case CLOSE:
// valid at READ COMMITTED Isolation
break;
default:
performRollback();
if (onQueryOnly == OnQueryOnly.COMMIT) {
performCommit();
} else {
performRollback();
}
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
@@ -900,6 +882,48 @@ public class JdbcTransaction implements SpiTransaction {
connection.commit();
}
/**
* Batch flush, jdbc commit, trigger registered TransactionCallbacks, notify l2 cache etc.
*/
private void flushCommitAndNotify() throws SQLException {
if (batchControl != null && !batchControl.isEmpty()) {
batchControl.flush();
}
firePreCommit();
// only performCommit can throw an exception
performCommit();
firePostCommit();
notifyCommit();
}
/**
* Perform a commit, fire callbacks and notify l2 cache etc.
* <p>
* This leaves the transaction active and expects another commit
* to occur later (which closes the underlying connection etc).
* </p>
*/
@Override
public void commitAndContinue() throws RollbackException {
if (rollbackOnly) {
return;
}
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
try {
flushCommitAndNotify();
// the event has been sent to the transaction manager
// for postCommit processing (l2 cache updates etc)
// start a new transaction event
event = new TransactionEvent();
} catch (Exception e) {
doRollback(e);
throw new RollbackException(e);
}
}
/**
* Commit the transaction.
*/
@@ -912,29 +936,19 @@ public class JdbcTransaction implements SpiTransaction {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
firePreCommit();
try {
if (queryOnly) {
// can rollback or just close for performance
connectionEndForQueryOnly();
} else {
// commit
if (batchControl != null && !batchControl.isEmpty()) {
batchControl.flush();
}
performCommit();
flushCommitAndNotify();
}
} catch (Exception e) {
doRollback(e);
throw new RollbackException(e);
} finally {
// these will not throw an exception
firePostCommit();
deactivate();
notifyCommit();
}
}
@@ -951,6 +965,14 @@ public class JdbcTransaction implements SpiTransaction {
}
}
/**
* Return true if the transaction is marked as rollback only.
*/
@Override
public boolean isRollbackOnly() {
return rollbackOnly;
}
/**
* Mark the transaction as rollback only.
*/
@@ -959,6 +981,16 @@ public class JdbcTransaction implements SpiTransaction {
this.rollbackOnly = true;
}
/**
* Perform rollback is the transaction is still active.
*/
@Override
public void rollbackIfActive() {
if (isActive()) {
rollback(null);
}
}
/**
* Rollback the transaction.
*/
@@ -976,17 +1008,26 @@ public class JdbcTransaction implements SpiTransaction {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
try {
doRollback(cause);
} finally {
deactivate();
}
}
/**
* Perform the jdbc rollback and fire any registered callbacks.
*/
private void doRollback(Throwable cause) {
firePreRollback();
try {
performRollback();
} catch (Exception ex) {
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
// these will not throw an exception
firePostRollback();
deactivate();
notifyRollback(cause);
}
}
@@ -4,8 +4,6 @@ import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import com.avaje.ebean.dbmigration.DbOffline;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeSet;
@@ -14,11 +12,10 @@ import com.avaje.ebeaninternal.api.TransactionEvent;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.bootup.BootupClasses;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import org.avaje.datasource.DataSourcePool;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import org.avaje.datasource.DataSourcePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,8 +89,6 @@ public class TransactionManager {
protected final BulkEventListenerMap bulkEventListenerMap;
protected final TransactionEventListener[] transactionEventListeners;
/**
* Used to prepare the change set setting user context information in the
* foreground thread before logging.
@@ -115,7 +110,7 @@ public class TransactionManager {
* Create the TransactionManager
*/
public TransactionManager(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr) {
this.skipCacheAfterWrite = config.isSkipCacheAfterWrite();
this.localL2Caching = localL2Caching;
@@ -133,9 +128,6 @@ public class TransactionManager {
this.docStoreUpdateProcessor = docStoreUpdateProcessor;
this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners());
List<TransactionEventListener> transactionEventListeners = bootupClasses.getTransactionEventListeners();
this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
this.prefix = "";
this.externalTransPrefix = "e";
@@ -192,51 +184,10 @@ public class TransactionManager {
return OnQueryOnly.valueOf(systemPropertyValue.trim().toUpperCase());
}
if (OnQueryOnly.CLOSE.equals(dbPlatformOnQueryOnly)) {
// check for read committed isolation level
if (!isReadCommittedIsolation(ds)) {
logger.warn("Ignoring DatabasePlatform.OnQueryOnly.CLOSE as the transaction Isolation Level is not READ_COMMITTED");
// we will just use ROLLBACK and ignore the desired optimisation
return OnQueryOnly.ROLLBACK;
} else {
// will use the OnQueryOnly.CLOSE optimisation
return OnQueryOnly.CLOSE;
}
}
// default to rollback if not defined on the platform
return dbPlatformOnQueryOnly == null ? OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly;
}
/**
* Return true if the isolation level is read committed.
*/
protected boolean isReadCommittedIsolation(DataSource ds) {
if (DbOffline.isSet()) {
return true;
}
Connection c = null;
try {
c = ds.getConnection();
int isolationLevel = c.getTransactionIsolation();
return (isolationLevel == Connection.TRANSACTION_READ_COMMITTED);
} catch (SQLException ex) {
String m = "Errored trying to determine the default Isolation Level";
throw new PersistenceException(m, ex);
} finally {
try {
if (c != null) {
c.close();
}
} catch (SQLException ex) {
logger.error("closing connection", ex);
}
}
}
public String getServerName() {
return serverName;
}
@@ -352,10 +303,6 @@ public class TransactionManager {
TXN_LOGGER.debug(msg);
}
for (TransactionEventListener listener : transactionEventListeners) {
listener.postTransactionRollback(transaction, cause);
}
} catch (Exception ex) {
logger.error("Error while notifying TransactionEventListener of rollback event", ex);
}
@@ -410,10 +357,6 @@ public class TransactionManager {
postCommit.notifyLocalCache();
backgroundExecutor.execute(postCommit.backgroundNotify());
for (TransactionEventListener listener : transactionEventListeners) {
listener.postTransactionCommit(transaction);
}
} catch (Exception ex) {
logger.error("NotifyOfCommit failed. L2 Cache potentially not notified.", ex);
}
@@ -13,7 +13,7 @@ public class BaseTestCase {
static {
logger.debug("... preStart");
if (!AgentLoader.loadAgentFromClasspath("avaje-ebeanorm-agent","debug=1;packages=com.avaje.tests,org.avaje.test")) {
if (!AgentLoader.loadAgentFromClasspath("ebean-agent","debug=1;packages=com.avaje.tests,org.avaje.test")) {
logger.info("avaje-ebeanorm-agent not found in classpath - not dynamically loaded");
}
}
@@ -186,7 +186,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public <T> List<Object> findIdsWithCopy(Query<T> query, Transaction t) {
public <A> List<A> findIdsWithCopy(Query<?> query, Transaction t) {
return null;
}
@@ -466,7 +466,12 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public <T> List<Object> findIds(Query<T> query, Transaction transaction) {
public <A> List<A> findIds(Query<?> query, Transaction transaction) {
return null;
}
@Override
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction) {
return null;
}
@@ -516,7 +521,12 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public <T> Map<?, T> findMap(Query<T> query, Transaction transaction) {
public <K, T> Map<K, T> findMap(Query<T> query, Transaction transaction) {
return null;
}
@Override
public <A> List<A> findSingleAttributeList(Query<?> query, Transaction transaction) {
return null;
}
@@ -1,17 +1,17 @@
package com.avaje.tests.basic;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FutureIds;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat;
public class TestFetchId extends BaseTestCase {
@@ -28,19 +28,12 @@ public class TestFetchId extends BaseTestCase {
.query();
List<Object> ids = Ebean.getServer(null).findIds(query, null);
assertThat(ids).isNotEmpty();
FutureIds<Order> futureIds = Ebean.getServer(null).findFutureIds(query,null);
// this list is likely empty at this point and
// will get populated in the background
List<Object> partial = futureIds.getPartialIds();
// this is likely 0 or a small number
// wait for all the id's to be fetched
List<Object> idList = futureIds.get();
Assert.assertTrue("same instance", partial == idList);
Assert.assertTrue("sz > 0", !ids.isEmpty());
assertThat(idList).isNotEmpty();
}
}
@@ -22,7 +22,7 @@ public class TestLazyLoadInCache extends BaseTestCase {
ResetBasicData.reset();
Map<?, Customer> map = Ebean.find(Customer.class)
Map<Integer, Customer> map = Ebean.find(Customer.class)
.select("id, name")
.setLoadBeanCache(true)
.setReadOnly(true)
@@ -17,7 +17,7 @@ public class TestLoadBeanCache extends BaseTestCase {
ResetBasicData.reset();
Map<?, Country> map = Ebean.find(Country.class)
Map<String, Country> map = Ebean.find(Country.class)
.setLoadBeanCache(true)
.setUseQueryCache(true)
.setReadOnly(true)
@@ -1,43 +0,0 @@
package com.avaje.tests.basic.event;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.event.TransactionEventListener;
public class MyTestTransactionEventListener implements TransactionEventListener {
private volatile static boolean doTest = false;
private static Transaction lastCommitted;
private static Transaction lastRollbacked;
public void postTransactionCommit(Transaction tx) {
if (!doTest) {
return;
}
lastCommitted = tx;
}
public void postTransactionRollback(Transaction tx, Throwable cause) {
if (!doTest) {
return;
}
lastRollbacked = tx;
}
public static void setDoTest(boolean doTest) {
MyTestTransactionEventListener.doTest = doTest;
// reset what we've recorded so far
lastCommitted = null;
lastRollbacked = null;
}
public static Transaction getLastCommitted() {
return lastCommitted;
}
public static Transaction getLastRollbacked() {
return lastRollbacked;
}
}
@@ -1,62 +0,0 @@
package com.avaje.tests.basic.event;
import junit.framework.TestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.tests.model.basic.TWithPreInsert;
public class TestTransactionEvent extends TestCase {
@Override
protected void tearDown() throws Exception {
MyTestTransactionEventListener.setDoTest(false);
}
@Override
protected void setUp() throws Exception {
MyTestTransactionEventListener.setDoTest(true);
}
public void test() {
assertNull(MyTestTransactionEventListener.getLastCommitted());
assertNull(MyTestTransactionEventListener.getLastRollbacked());
final Object myUserObject = new Object();
Transaction tx = Ebean.beginTransaction();
tx.putUserObject("myUserObject", myUserObject);
TWithPreInsert e = new TWithPreInsert();
e.setTitle("Mister Transaction1");
Ebean.save(e);
tx.commit();
assertNotNull(MyTestTransactionEventListener.getLastCommitted());
assertNotNull(MyTestTransactionEventListener.getLastCommitted().getUserObject("myUserObject"));
assertSame(MyTestTransactionEventListener.getLastCommitted().getUserObject("myUserObject"), myUserObject);
assertNull(MyTestTransactionEventListener.getLastRollbacked());
Transaction tx2 = Ebean.beginTransaction();
tx2.putUserObject("myUserObject2", myUserObject);
TWithPreInsert e2 = new TWithPreInsert();
e2.setTitle("Mister Transaction2");
Ebean.save(e2);
tx2.rollback();
assertNotNull(MyTestTransactionEventListener.getLastCommitted());
assertNotNull(MyTestTransactionEventListener.getLastRollbacked());
assertNotSame(MyTestTransactionEventListener.getLastCommitted(), MyTestTransactionEventListener.getLastRollbacked());
assertNotNull(MyTestTransactionEventListener.getLastCommitted().getUserObject("myUserObject"));
assertSame(MyTestTransactionEventListener.getLastCommitted().getUserObject("myUserObject"), myUserObject);
assertNotNull(MyTestTransactionEventListener.getLastRollbacked().getUserObject("myUserObject2"));
assertSame(MyTestTransactionEventListener.getLastRollbacked().getUserObject("myUserObject2"), myUserObject);
}
}
@@ -19,6 +19,13 @@ public class MnyB extends BaseModel {
@ManyToMany(cascade = CascadeType.REMOVE)
List<MnyC> cs;
public MnyB(String name) {
this.name = name;
}
public MnyB() {
}
public String getName() {
return name;
}
@@ -27,6 +27,32 @@ public class TestQueryFindIterate extends BaseTestCase {
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
.setMaxRows(2);
final AtomicInteger count = new AtomicInteger();
QueryIterator<Customer> it = query.findIterate();
try {
while (it.hasNext()) {
Customer customer = it.next();
customer.getName();
count.incrementAndGet();
}
} finally {
it.close();
}
assertEquals(2, count.get());
}
@Test
public void findEach() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
.setAutoTune(false)
//.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).orderBy("id")
@@ -1,14 +1,16 @@
package com.avaje.tests.query;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.Product;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
public class TestQueryFindMapTypedKey extends BaseTestCase {
@@ -17,9 +19,21 @@ public class TestQueryFindMapTypedKey extends BaseTestCase {
ResetBasicData.reset();
Map<String, Customer> map = Ebean.find(Customer.class).select("id, name")
.findMap("name", String.class);
Map<String, Product> productsBySku = Ebean.find(Product.class)
.setMapKey("sku")
.findMap();
assertThat(productsBySku).isNotEmpty();
Product desk = productsBySku.get("DSK1");
assertNotNull(desk);
Map<String, Customer> map = Ebean.find(Customer.class)
.select("id, name")
.setMapKey("name")
.findMap();
assertNotNull(map);
Assert.assertNotNull(map);
}
}
@@ -23,7 +23,7 @@ public class TestQueryPlanCacheRowCount extends BaseTestCase {
int rc0 = query.findRowCount();
List<Object> ids = query.findIds();
List<Integer> ids = query.findIds();
Assert.assertEquals(rc0, ids.size());
List<Order> list0 = query.findList();
@@ -32,7 +32,7 @@ public class TestQueryPlanCacheRowCount extends BaseTestCase {
int rc1 = query.findCount();
Assert.assertEquals(rc0, rc1);
List<Object> ids1 = query.findIds();
List<Integer> ids1 = query.findIds();
Assert.assertEquals(rc0, ids1.size());
List<Order> list1 = query.findList();
@@ -53,7 +53,7 @@ public class TestQueryPlanCacheRowCount extends BaseTestCase {
System.out.println("Expection Not same " + rc0 + " != " + rc2);
Assert.assertNotSame(rc0, rc2);
List<Object> ids2 = query2.findIds();
List<Integer> ids2 = query2.findIds();
Assert.assertEquals(rc2, ids2.size());
List<Order> list2 = query2.findList();
@@ -20,8 +20,7 @@ public class TestFindIdsWithInheritance extends BaseTestCase {
Ebean.save(truck);
List<Object> ids = Ebean.find(Vehicle.class).findIds();
List<Integer> ids = Ebean.find(Vehicle.class).findIds();
Assert.assertNotNull(ids);
Ebean.delete(truck);
@@ -95,7 +95,7 @@ public class TestObjectGraphNodeStatsCollection extends BaseTestCase {
ResetBasicData.reset();
List<Object> ids = Ebean.find(Order.class).findIds();
List<Integer> ids = Ebean.find(Order.class).findIds();
Assert.assertTrue(!ids.isEmpty());
}
@@ -0,0 +1,128 @@
package com.avaje.tests.query.other;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import org.junit.Test;
import java.sql.Date;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestQuerySingleAttribute extends BaseTestCase {
@Test
public void exampleUsage() {
ResetBasicData.reset();
List<String> names =
Ebean.find(Customer.class)
.setDistinct(true)
.select("name")
.where().eq("status", Customer.Status.NEW)
.orderBy().asc("name")
.setMaxRows(100)
.findSingleAttributeList();
assertThat(names).isNotNull();
}
@Test
public void exampleUsage_otherType() {
ResetBasicData.reset();
List<Date> dates =
Ebean.find(Customer.class)
.setDistinct(true)
.select("anniversary")
.where().isNotNull("anniversary")
.orderBy().asc("anniversary")
.findSingleAttributeList();
assertThat(dates).isNotNull();
}
@Test
public void withOrderBy() {
Query<Customer> query =
Ebean.find(Customer.class)
.setDistinct(true)
.select("name")
.where().eq("status", Customer.Status.NEW)
.orderBy().asc("name")
.setMaxRows(100);
query.findSingleAttributeList();
assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 where t0.status = ? order by t0.name ");
}
@Test
public void basic() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class).select("name");
List<String> names = query.findSingleAttributeList();//String.class);
assertThat(query.getGeneratedSql()).contains("select t0.name c0 from o_customer t0");
assertThat(names).isNotNull();
}
@Test
public void distinctAndWhere() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.select("name")
.where().eq("status", Customer.Status.NEW)
.query();
List<String> names = query.findSingleAttributeList();
assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 where t0.status = ? ");
assertThat(names).isNotNull();
}
@Test
public void distinctWhereWithJoin() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
.setDistinct(true)
.select("name")
.where().eq("status", Customer.Status.NEW)
.istartsWith("billingAddress.city", "auck")
.query();
List<String> names = query.findSingleAttributeList();
assertThat(query.getGeneratedSql()).contains("select distinct t0.name c0 from o_customer t0 left outer join o_address t1 on t1.id = t0.billing_address_id where t0.status = ? and lower(t1.city) like ?");
assertThat(names).isNotNull();
}
@Test
public void queryPlan_expect_differentPlans() {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class).select("name");
query.findSingleAttributeList();
assertThat(query.getGeneratedSql()).contains("select t0.name c0 from o_customer t0");
Query<Customer> query2 = Ebean.find(Customer.class).select("name");
query2.findList();
assertThat(query2.getGeneratedSql()).contains("select t0.id c0, t0.name c1 from o_customer t0");
}
}
@@ -257,7 +257,7 @@ public class TestReadAudit extends BaseTestCase {
resetCounters();
Map<?,EBasicChangeLog> list = server.find(EBasicChangeLog.class)
Map<Long,EBasicChangeLog> list = server.find(EBasicChangeLog.class)
.where().startsWith("shortDescription", "readAudit")
.findMap();
@@ -41,7 +41,7 @@ public class TestJsonMap extends BaseTestCase {
ResetBasicData.reset();
Map<String, Customer> map = Ebean.find(Customer.class).findMap("id", String.class);
Map<String, Customer> map = Ebean.find(Customer.class).findMap();
JsonContext jsonContext = Ebean.json();
JsonWriteOptions options = JsonWriteOptions.parsePath("(id,status,name)");
@@ -0,0 +1,99 @@
package com.avaje.tests.transaction;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.tests.model.m2m.MnyB;
import com.avaje.tests.model.m2m.MnyC;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
public class TestBeanStateReset extends BaseTestCase {
private static final Logger logger = LoggerFactory.getLogger(TestBeanStateReset.class);
@Test
public void resetForInsert() {
// setup to fail foreign key constraint
MnyC c = new MnyC();
c.setId(Long.MAX_VALUE);
MnyB b = new MnyB();
b.getCs().add(c);
try {
// inserts of b succeeds but intersection insert fails FK check on c
b.save();
} catch (PersistenceException e) {
logger.info("expected error " + e.getMessage());
Ebean.getBeanState(b).resetForInsert();
b.getCs().clear();
LoggedSqlCollector.start();
b.setName("mod");
b.save();
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("insert into mny_b (id, name, version, when_created, when_modified, a_id) values (");
}
}
@Test
public void alternativeVia_persistCascadeOff_with_commitAndContinue() {
// setup to fail foreign key constraint when using cascade save
MnyC c = new MnyC();
c.setId(Long.MAX_VALUE);
MnyB b = new MnyB();
b.getCs().add(c);
Transaction transaction = Ebean.beginTransaction();
try {
// turn off cascade ...
transaction.setPersistCascade(false);
b.save();
// commit at this point
transaction.commitAndContinue();
try {
// turn on cascade ... such that the ManyToMany is persisted
transaction.setPersistCascade(true);
// save b again which this time cascades to the ManyToMany
// but this fails due to FK on ManyToMany
b.save();
// we actually don't get here due to the FK error
transaction.commit();
} catch (PersistenceException e) {
// so we failed the second save but that is ok'ish
// we handle this exception knowing b got inserted and committed
// and that the inserts into the intersection table failed
logger.info("The ManyToMany intersection error: " + e.getMessage());
}
} finally {
// performs a rollback as the commit at line:80 does not happen
transaction.end();
}
// assert our insert prior to the commitAndContinue succeeded
MnyB madeIt = Ebean.find(MnyB.class, b.getId());
assertNotNull(madeIt);
}
}
@@ -0,0 +1,183 @@
package com.avaje.tests.transaction;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.Transactional;
import com.avaje.tests.model.m2m.MnyB;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class TestCommitAndContinue extends BaseTestCase {
private static final Logger logger = LoggerFactory.getLogger("org.avaje.ebean.TXN");
@Test
@Transactional
public void transactional_partialSuccess() {
MnyB a = new MnyB("a100");
MnyB b = new MnyB("b200");
a.save();
// commit at this point
Ebean.currentTransaction().commitAndContinue();
try {
b.save();
// some error occurs
throw new IllegalStateException();
} catch (IllegalStateException e) {
// mark the transaction as rollback
Ebean.currentTransaction().setRollbackOnly();
// use a different transaction to assert
EbeanServer server = Ebean.getDefaultServer();
Transaction anotherTxn = server.createTransaction();
// success prior to commitAndContinue
assertNotNull(server.find(MnyB.class, a.getId(), anotherTxn));
// insert failed after commitAndContinue
assertNull(server.find(MnyB.class, b.getId(), anotherTxn));
}
}
/**
* The @Transactional is nicer to me.
*/
@Test
public void tryFinally_partialSuccess() {
MnyB a = new MnyB("a100");
MnyB b = new MnyB("b200");
EbeanServer server = Ebean.getDefaultServer();
Transaction txn = server.beginTransaction();
try {
a.save();
// commit at this point
txn.commitAndContinue();
try {
b.save();
// some error occurs
throw new IllegalStateException();
} catch (IllegalStateException e) {
// mark the transaction as rollback
txn.setRollbackOnly();
// use a different transaction to assert
Transaction anotherTxn = server.createTransaction();
// success prior to commitAndContinue
assertNotNull(server.find(MnyB.class, a.getId(), anotherTxn));
// insert failed after commitAndContinue
assertNull(server.find(MnyB.class, b.getId(), anotherTxn));
}
// does not commit due to the txn.setRollbackOnly();
txn.commit();
} finally {
server.endTransaction();
}
}
@Test
@Transactional
public void transactional_partialSuccess_secondTransactionInsert() {
MnyB a = new MnyB("a100");
MnyB b = new MnyB("b200");
MnyB c = new MnyB("c300");
a.save();
// commit at this point
Ebean.currentTransaction().commitAndContinue();
try {
b.save();
// some error occurs
throw new IllegalStateException();
} catch (IllegalStateException e) {
// mark the transaction as rollback
Ebean.currentTransaction().setRollbackOnly();
// use a different transaction to do something useful
EbeanServer server = Ebean.getDefaultServer();
Transaction txn2 = server.createTransaction();
try {
server.save(c, txn2);
txn2.commit();
} finally {
txn2.end();
}
}
// asserts
EbeanServer server = Ebean.getDefaultServer();
Transaction txnForAssert = server.createTransaction();
// success prior to commitAndContinue
assertNotNull(server.find(MnyB.class, a.getId(), txnForAssert));
// insert failed after commitAndContinue
assertNull(server.find(MnyB.class, b.getId(), txnForAssert));
// successful insert using txn2
assertNotNull(server.find(MnyB.class, c.getId(), txnForAssert));
}
@Test
public void basic() {
MnyB a = new MnyB("a");
MnyB b = new MnyB("b");
MnyB c = new MnyB("c");
Transaction txn = Ebean.beginTransaction();
try {
a.save();
txn.commitAndContinue();
txn.setBatchMode(true);
b.save();
logger.info("... pre commitAndContinue");
txn.commitAndContinue();
c.save();
txn.commit();
} finally {
txn.end();
}
}
@Test
@Transactional
public void runTransactional() {
new MnyB("a100").save();
new MnyB("a101").save();
Ebean.currentTransaction().commitAndContinue();
new MnyB("a200").save();
new MnyB("a201").save();
}
}
@@ -3,6 +3,7 @@ package com.avaje.tests.transaction;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.TransactionCallbackAdapter;
import org.junit.Test;
@@ -27,8 +28,9 @@ public class TestTransactionCallback extends BaseTestCase {
public void test_commitAndRollback() {
Ebean.beginTransaction();
Transaction txn = Ebean.beginTransaction();
Ebean.register(new MyCallback());
txn.getConnection();
Ebean.commitTransaction();
assertEquals(1, countPreCommit);
@@ -7,6 +7,8 @@ import com.avaje.tests.model.basic.EBasic;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class TestTransactionRollbackOnly {
@@ -30,7 +32,10 @@ public class TestTransactionRollbackOnly {
Ebean.save(one);
Transaction transaction = Ebean.currentTransaction();
assertFalse(transaction.isRollbackOnly());
transaction.setRollbackOnly();
assertTrue(transaction.isRollbackOnly());
}
@Test