Compare commits

...
54 changed files with 4665 additions and 2563 deletions
+1 -1
View File
@@ -7,6 +7,6 @@ Maven Dependency
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.3.1</version>
<version>4.4.1</version>
</dependency>
+30 -4
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.4.1</version>
<version>4.5.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -89,6 +89,13 @@
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -99,7 +106,7 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.1.10</version>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
@@ -191,7 +198,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.1.9</version>
<version>4.5.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -241,8 +248,27 @@
<doctitle>Ebean 4</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<!-- <excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames> -->
<additionalparam>-Xdoclint:none</additionalparam>
<!--<additionalparam>-Xdoclint:none</additionalparam>-->
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
<version>1.0.0</version>
</docletArtifact>
<additionalparam>
-Xdoclint:none
-include-basedir ${project.basedir}
-attributes "idseparator=-; project_name=${project.name}; \
project_version=${project.version}; \
project_desc=${project.description}"
</additionalparam>
<linksource>true</linksource>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
@@ -81,10 +81,6 @@ public interface BeanState {
* {@link EbeanServer#createEntityBean(Class)}, then populate its properties
* and then call this method specifying which properties where loaded or null
* for a fully loaded entity bean.
*
* @param loadedProperties
* the properties that where loaded or null for a fully loaded entity
* bean.
*/
public void setLoaded();
}
+3 -3
View File
@@ -671,7 +671,8 @@ public final class Ebean {
/**
* Refresh the values of a bean.
* <p>
* Note that this does not refresh any OneToMany or ManyToMany properties.
* Note that this resets OneToMany and ManyToMany properties so that if they
* are accessed a lazy load will refresh the many property.
* </p>
*/
public static void refresh(Object bean) {
@@ -1376,8 +1377,7 @@ public final class Ebean {
/**
* Return the BeanState for a given entity bean.
* <p>
* This will return null if the bean is not an enhanced (or subclassed) entity
* bean.
* This will return null if the bean is not an enhanced entity bean.
* </p>
*/
public static BeanState getBeanState(Object bean) {
File diff suppressed because it is too large Load Diff
+103 -2
View File
@@ -24,11 +24,94 @@ import javax.persistence.MappedSuperclass;
* <p>
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
* or if you believe it 'pollutes' your entity beans.
*
*
* <p>
* You can use Dependency Injection like Guice or Spring to construct and wire a EbeanServer instance
* and have that same instance used with this Model and Finder. The way that works is that when the
* DI container creates the EbeanServer instance it can be registered with the Ebean singleton. In this
* way the EbeanServer instance can be injected as per normal Guice / Spring dependency injection and
* that same instance also used to support the Model and Finder active record style.
*
* <p>
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
* a {@link Finder} as a public static field to complete the active record pattern and provide a
* relatively nice clean way to write queries.
*
* <h3>Typical common @MappedSuperclass</h3>
* <pre>{@code
*
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* @MappedSuperclass
* public class BaseModel extends Model {
*
* @Id Long id;
*
* @Version Long version;
*
* @CreatedTimestamp Timestamp whenCreated;
*
* @UpdatedTimestamp Timestamp whenUpdated;
*
* ...
*
* }</pre>
*
* <h3>Extend the Model</h3>
* <pre>{@code
*
* // Extend the mappedSuperclass
*
* @Entity @Table(name="oto_account")
* public class Account extends BaseModel {
*
* // add a static Finder
* // ... with Long being the type of our ID property ...
*
* public static final Finder<Long,Account> find =
* new Finder<Long,Account>(Long.class, Account.class);
*
* String name;
*
* @OneToOne(mappedBy = "account",optional = true)
* User user;
*
* ...
* }
*
* }</pre>
*
* <h3>Modal: save()</h3>
* <pre>{@code
*
* // Active record style ... save(), delete() etc
* Account account = new Account();
* account.setName("AC234");
*
* // save() method inherited from Model
* account.save();
*
* }</pre>
*
* <h3>Finder: find byId</h3>
* <pre>{@code
*
* // find byId
* Account account = Account.find.byId(42);
*
* }</pre>
*
* <h3>Finder: find where</h3>
* <pre>{@code
*
* // find where ...
* List<Account> accounts =
* Account.find
* .where().gt("startDate", lastMonth)
* .findList();
*
* }</pre>
*/
@MappedSuperclass
public abstract class Model {
@@ -105,7 +188,9 @@ public abstract class Model {
* customer.markAsDirty();
* customer.save();
*
* </pre>
* </pre>
*
* @see EbeanServer#markAsDirty(Object)
*/
public void markAsDirty() {
db().markAsDirty(this);
@@ -117,6 +202,8 @@ public abstract class Model {
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see EbeanServer#save(Object)
*/
public void save() {
db().save(this);
@@ -124,6 +211,8 @@ public abstract class Model {
/**
* Update this entity.
*
* @see EbeanServer#update(Object)
*/
public void update() {
db().update(this);
@@ -131,6 +220,8 @@ public abstract class Model {
/**
* Insert this entity.
*
* @see EbeanServer#insert(Object)
*/
public void insert() {
db().insert(this);
@@ -138,6 +229,8 @@ public abstract class Model {
/**
* Delete this entity.
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
@@ -166,6 +259,8 @@ public abstract class Model {
/**
* Refreshes this entity from the database.
*
* @see EbeanServer#refresh(Object)
*/
public void refresh() {
db().refresh(this);
@@ -250,6 +345,8 @@ public abstract class Model {
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
@@ -306,6 +403,8 @@ public abstract class Model {
/**
* Creates a query applying the path properties to set the select and fetch clauses.
* <p>
* Equivalent to {@link Query#apply(com.avaje.ebean.text.PathProperties)}
*/
public Query<T> apply(PathProperties pathProperties) {
return db().find(type).apply(pathProperties);
@@ -346,6 +445,8 @@ public abstract class Model {
/**
* Execute the query consuming each bean one at a time.
* <p>
* Equivalent to {@link Query#findEachWhile(QueryEachWhileConsumer)}
* <p>
* This is similar to #findEach except that you return boolean
* true to continue processing beans and return false to stop
* processing early.
-74
View File
@@ -1,74 +0,0 @@
package com.avaje.ebean;
import java.util.List;
/**
* Represents a Page of results that is part of a PagingList.
* <p>
* Typically a Page represents the data that is shown to the user at a single
* time - and the user 'pages' through a large list.
* </p>
*
* @author rbygrave
*
* @param <T>
* the entity bean type
*
* @see Query#findPagingList(int)
* @see PagingList
*/
public interface Page<T> {
/**
* Return the list of entities for this page.
*/
public List<T> getList();
/**
* Return the total row count for all pages.
*/
public int getTotalRowCount();
/**
* Return the total number of pages.
*/
public int getTotalPageCount();
/**
* Return the index position of this page.
*/
public int getPageIndex();
/**
* Return true if there is a next page.
*/
public boolean hasNext();
/**
* Return true if there is a previous page.
*/
public boolean hasPrev();
/**
* Return the next page.
*/
public Page<T> next();
/**
* Return the previous page.
*/
public Page<T> prev();
/**
* Helper method to return a "X to Y of Z" string for this page where X is the
* first row, Y the last row and Z the total row count.
*
* @param to
* String to put between the first and last row
* @param of
* String to put between the last row and the total row count
*
* @return String of the format XtoYofZ.
*/
public String getDisplayXtoYofZ(String to, String of);
}
+115 -3
View File
@@ -16,7 +16,44 @@ import java.util.concurrent.Future;
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <h4>Example: No total row count required</h4>
* <pre>{@code
*
* // If you are not getting the 'first page' often
* // you do not bother getting the total row count again
* // so instead just get the page list of data
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* }</pre>
*
* @param <T>
* the entity bean type
*
@@ -26,12 +63,53 @@ public interface PagedList<T> {
/**
* Initiate the loading of the total row count in the background.
* <pre>{@code
*
* // initiate the loading of the total row count
* // in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* <p>
* Also note that using loadRowCount() and getTotalRowCount() rather than getFutureRowCount()
* means that exceptions ExecutionException, InterruptedException, TimeoutException are instead
* wrapped in the unchecked PersistenceException (which might be preferrable).
* </p>
*/
public void loadRowCount();
/**
* Return the Future row count. You might get this if you wish to cancel the total row count query
* or specify a timeout for that query.
* or specify a timeout for the row count query.
* <p>
* The loadRowCount() & getTotalRowCount() methods internally make use of this getFutureRowCount() method.
* Generally I expect people to prefer loadRowCount() & getTotalRowCount() over getFutureRowCount().
* </p>
* <pre>{@code
*
* // initiate the row count query in the background thread
* Future<Integer> rowCount = pagedList.getFutureRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // now get the total count with a timeout
* Integer totalRowCount = rowCount.get(30, TimeUnit.SECONDS);
*
* // or ge the total count without a timeout
* Integer totalRowCountViaFuture = rowCount.get();
*
* // which is actually the same as ...
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public Future<Integer> getFutureRowCount();
@@ -42,11 +120,37 @@ public interface PagedList<T> {
/**
* Return the total row count for all pages.
* <p>
* If loadRowCount() has already been called then the row count query is already executing in a background thread
* and this gets the associated Future and gets the value waiting for the future to finish.
* </p>
* <p>
* If loadRowCount() has not been called then this executes the find row count query and returns the result and this
* will just occur in the current thread and not use a background thread.
* </p>
* <pre>{@code
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (which was being executed
* // in a background thread if loadRowCount() was used)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
public int getTotalRowCount();
/**
* Return the total number of pages based on the page size and total row count.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public int getTotalPageCount();
@@ -57,6 +161,10 @@ public interface PagedList<T> {
/**
* Return true if there is a next page.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
public boolean hasNext();
@@ -68,7 +176,11 @@ public interface PagedList<T> {
/**
* Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the
* last row and Z the total row count.
*
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*
* @param to
* String to put between the first and last row
* @param of
+291 -174
View File
@@ -13,54 +13,57 @@ import java.util.Set;
* Example: Create the query using the API.
* </p>
*
* <pre class="code">
* List&lt;Order&gt; orderList =
* Ebean.find(Order.class)
* .fetch(&quot;customer&quot;)
* .fetch(&quot;details&quot;)
* <pre>{@code
*
* List<Order> orderList =
* ebeanServer.find(Order.class)
* .fetch("customer")
* .fetch("details")
* .where()
* .like(&quot;customer.name&quot;,&quot;rob%&quot;)
* .gt(&quot;orderDate&quot;,lastWeek)
* .orderBy(&quot;customer.id, id desc&quot;)
* .like("customer.name","rob%")
* .gt("orderDate",lastWeek)
* .orderBy("customer.id, id desc")
* .setMaxRows(50)
* .findList();
*
* ...
* </pre>
* }</pre>
*
* <p>
* Example: The same query using the query language
* </p>
*
* <pre class="code">
* <pre>{@code
*
* String oql =
* &quot; find order &quot;
* +&quot; fetch customer &quot;
* +&quot; fetch details &quot;
* +&quot; where customer.name like :custName and orderDate &gt; :minOrderDate &quot;
* +&quot; order by customer.id, id desc &quot;
* +&quot; limit 50 &quot;;
* " find order "
* +" fetch customer "
* +" fetch details "
* +" where customer.name like :custName and orderDate > :minOrderDate "
* +" order by customer.id, id desc "
* +" limit 50 ";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <p>
* Example: Using a named query called "with.cust.and.details"
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createNamedQuery(Order.class,&quot;with.cust.and.details&quot;);
* query.setParameter(&quot;custName&quot;, &quot;Rob%&quot;);
* query.setParameter(&quot;minOrderDate&quot;, lastWeek);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createNamedQuery(Order.class,"with.cust.and.details");
* query.setParameter("custName", "Rob%");
* query.setParameter("minOrderDate", lastWeek);
*
* List&lt;Order&gt; orderList = query.findList();
* List<Order> orderList = query.findList();
* ...
* </pre>
* }</pre>
*
* <h3>Autofetch</h3>
* <p>
@@ -98,13 +101,13 @@ import java.util.Set;
* Refer to "ALL Properties/Columns" mode of Optimistic Concurrency checking.
* </p>
*
* <pre class="code">
* <pre>{@code
* [ find {bean type} [ ( * | {fetch properties} ) ] ]
* [ fetch {associated bean} [ ( * | {fetch properties} ) ] ]
* [ where {predicates} ]
* [ order by {order by properties} ]
* [ limit {max rows} [ offset {first row} ] ]
* </pre>
* }</pre>
*
* <p>
* <b>FIND</b> <b>{bean type}</b> [ ( <i>*</i> | <i>{fetch properties}</i> ) ]
@@ -160,17 +163,17 @@ import java.util.Set;
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* </pre>
* }</pre>
*
* <p>
* Find orders fetching all its properties
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (*)
* </pre>
* }</pre>
*
* <p>
* Find orders fetching its id, shipDate and status properties. Note that the id
@@ -178,30 +181,30 @@ import java.util.Set;
* properties.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order (shipDate, status)
* </pre>
* }</pre>
*
* <p>
* Find orders with a named bind variable (that will need to be bound via
* {@link Query#setParameter(String, Object)}).
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* where customer.name like :custLike
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer with a named bind parameter. This
* will fetch and populate both the order and customer objects.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer
* where customer.id = :custId
* </pre>
* }</pre>
*
* <p>
* Find orders and also fetch the customer, customer shippingAddress, order
@@ -212,13 +215,13 @@ import java.util.Set;
* populated.
* </p>
*
* <pre class="code">
* <pre>{@code
* find order
* fetch customer (name)
* fetch customer.shippingAddress
* fetch details
* fetch details.product (sku, name)
* </pre>
* }</pre>
*
* <h3>Early parsing of the Query</h3>
* <p>
@@ -352,19 +355,24 @@ public interface Query<T> extends Serializable {
/**
* Explicitly set a comma delimited list of the properties to fetch on the
* 'main' entity bean (aka partial object). Note that '*' means all
* 'main' root level entity bean (aka partial object). Note that '*' means all
* properties.
* <p>
* You use {@link #fetch(String, String)} to specify specific properties to fetch
* on other non-root level paths of the object graph.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
* <pre>{@code
*
* // Only fetch the customer id, name and status.
* // This is described as a &quot;Partial Object&quot;
* query.select(&quot;name, status&quot;);
* query.where(&quot;lower(name) like :custname&quot;).setParameter(&quot;custname&quot;, &quot;rob%&quot;);
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // Only fetch the customer id, name and status.
* // This is described as a "Partial Object"
* .select("name, status")
* .where.ilike("name", "rob%")
* .findList();
*
* List&lt;Customer&gt; customerList = query.findList();
* </pre>
* }</pre>
*
* @param fetchProperties
* the properties to fetch for this bean (* = all properties).
@@ -383,31 +391,35 @@ public interface Query<T> extends Serializable {
* "Partial Object" - a bean that only has some of its properties populated.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // query orders...
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* List<Order> orders =
* ebeanserver.find(Order.class)
* // fetch the customer...
* // ... getting the customers name and phone number
* .fetch("customer", "name, phoneNumber")
*
* // fetch the customer...
* // ... getting the customer's name and phone number
* query.fetch(&quot;customer&quot;, &quot;name, phNumber&quot;);
*
* // ... also fetch the customers billing address (* = all properties)
* query.fetch(&quot;customer.billingAddress&quot;, &quot;*&quot;);
* </pre>
* // ... also fetch the customers billing address (* = all properties)
* .fetch("customer.billingAddress", "*")
* .findList();
* }</pre>
*
* <p>
* If columns is null or "*" then all columns/properties for that path are
* fetched.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // fetch customers (their id, name and status)
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
*
* // only fetch some of the properties of the customers
* query.select(&quot;name, status&quot;);
* List&lt;Customer&gt; list = query.findList();
* </pre>
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email")
* .findList();
*
* }</pre>
*
* @param path
* the path of an associated (1-1,1-M,M-1,M-M) bean.
@@ -420,6 +432,17 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a FetchConfig to use a separate query or lazy loading
* to load this path.
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String assocProperty, String fetchProperties, FetchConfig fetchConfig);
@@ -428,7 +451,17 @@ public interface Query<T> extends Serializable {
* <p>
* The same as {@link #fetch(String, String)} with the fetchProperties as "*".
* </p>
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // eager fetch the contacts
* .fetch("contacts")
* .findList();
*
* }</pre>
*
* @param path
* the property of an associated (1-1,1-M,M-1,M-M) bean.
*/
@@ -437,6 +470,18 @@ public interface Query<T> extends Serializable {
/**
* Additionally specify a JoinConfig to specify a "query join" and or define
* the lazy loading query.
*
*
* <pre>{@code
*
* // fetch customers (their id, name and status)
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", new FetchConfig().lazy(100))
* .findList();
*
* }</pre>
*/
public Query<T> fetch(String path, FetchConfig joinConfig);
@@ -466,6 +511,10 @@ public interface Query<T> extends Serializable {
* (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>
*/
@@ -493,6 +542,12 @@ public interface Query<T> extends Serializable {
* (unlike #findList #findSet etc)
* </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
* generally preferable.
* </p>
* <p>
* Compared with #findEachWhile this will always process all the beans where as
* #findEachWhile provides a way to stop processing the query result early before
* all the beans have been read.
@@ -503,19 +558,18 @@ public interface Query<T> extends Serializable {
* with Java8 closures.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
* ebeanServer.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .findEach((Customer customer) -> {
*
* query.findVisit((Customer customer) -> {
* // do something with customer
* System.out.println("-- visit " + customer);
* });
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* });
* </pre>
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
@@ -532,23 +586,23 @@ public interface Query<T> extends Serializable {
* </p>
*
* <pre class="code">
* <pre>{@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);
* ebeanServer.find(Customer.class)
* .fetch("contacts", new FetchConfig().query(2))
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
* .findEachWhile((Customer customer) -> {
*
* query.findEachWhile((Customer customer) -> {
* // do something with customer
* System.out.println("-- visit " + customer);
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
*
* // return true to continue processing or false to stop
* return (customer.getId() < 40);
* });
* </pre>
* }</pre>
*
* @param consumer
* the consumer used to process the queried beans.
@@ -560,7 +614,16 @@ public interface Query<T> extends Serializable {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
public List<T> findList();
@@ -570,7 +633,16 @@ public interface Query<T> extends Serializable {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
*
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
public Set<T> findSet();
@@ -585,11 +657,14 @@ public interface Query<T> extends Serializable {
* on the map. If one is not specified then the id property is used.
* </p>
*
* <pre class="code">
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
* query.setMapKey(&quot;sku&quot;);
* Map&lt;?, Product&gt; map = query.findMap();
* </pre>
* <pre>{@code
*
* Map<?, Product> map =
* ebeanServer.find(Product.class)
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@@ -612,32 +687,34 @@ public interface Query<T> extends Serializable {
* return 0 or 1 results.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // assuming the sku of products is unique...
* Product product =
* Ebean.find(Product.class)
* .where(&quot;sku = ?&quot;)
* .set(1, &quot;aa113&quot;)
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* ...
* </pre>
* }</pre>
*
* <p>
* It is also useful with finding objects by their id when you want to specify
* further join information.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Fetch order 1 and additionally fetch join its order details...
* Order order =
* Ebean.find(Order.class)
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch(&quot;details&quot;)
* .fetch("details")
* .findUnique();
*
* List&lt;OrderDetail&gt; details = order.getDetails();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
* ...
* </pre>
* }</pre>
*/
public T findUnique();
@@ -696,7 +773,32 @@ public interface Query<T> extends Serializable {
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
*
*
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 100 new orders
* // ... 0 means first page
* // ... page size is 100
*
* PagedList<Order> pagedList
* = ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findPagedList(0, 100);
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @param pageIndex
* The zero based index of the page.
* @param pageSize
@@ -706,19 +808,20 @@ public interface Query<T> extends Serializable {
public PagedList<T> findPagedList(int pageIndex, int pageSize);
/**
* Set a named bind parameter. Named parameters have a colon to prefix the
* name.
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a named parameter
* String oql = &quot;find order where status = :orderStatus&quot;;
* String oql = "find order where status = :orderStatus";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.find(Order.class, oql);
*
* // bind the named parameter
* query.bind(&quot;orderStatus&quot;, OrderStatus.NEW);
* List&lt;Order&gt; list = query.findList();
* </pre>
* query.bind("orderStatus", OrderStatus.NEW);
* List<Order> list = query.findList();
*
* }</pre>
*
* @param name
* the parameter name
@@ -732,17 +835,19 @@ public interface Query<T> extends Serializable {
* position starts at 1 to be consistent with JDBC PreparedStatement. You need
* to set a parameter value for each ? you have in the query.
*
* <pre class="code">
* <pre>{@code
*
* // a query with a positioned parameter
* String oql = &quot;where status = ? order by id desc&quot;;
* String oql = "where status = ? order by id desc";
*
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, oql);
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
*
* // bind the parameter
* query.setParameter(1, OrderStatus.NEW);
*
* List&lt;Order&gt; list = query.findList();
* </pre>
* List<Order> list = query.findList();
*
* }</pre>
*
* @param position
* the parameter bind position starting from 1 (not 0)
@@ -758,12 +863,18 @@ public interface Query<T> extends Serializable {
* fetch joins.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class);
* Order order = query.setId(1).join(&quot;details&quot;).findUnique();
* List&lt;OrderDetail&gt; details = order.getDetails();
* ...
* </pre>
* <pre>{@code
*
* Order order =
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
*
* }</pre>
*/
public Query<T> setId(Object id);
@@ -774,15 +885,17 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* <pre>{@code
*
* Query<Order> query = ebeanServer.createQuery(Order.class, "top");
* ...
* if (...) {
* query.where(&quot;status = :status and lower(customer.name) like :custName&quot;);
* query.setParameter(&quot;status&quot;, Order.NEW);
* query.setParameter(&quot;custName&quot;, &quot;rob%&quot;);
* query.where("status = :status and lower(customer.name) like :custName");
* query.setParameter("status", Order.NEW);
* query.setParameter("custName", "rob%");
* }
* </pre>
*
* }</pre>
*
* <p>
* Internally the addToWhereClause string is processed by removing named
@@ -802,13 +915,15 @@ public interface Query<T> extends Serializable {
/**
* Add a single Expression to the where clause returning the query.
*
* <pre class="code">
* List&lt;Order&gt; newOrders =
* Ebean.find(Order.class)
* .where().eq(&quot;status&quot;, Order.NEW)
* <pre>{@code
*
* List<Order> newOrders =
* ebeanServer.find(Order.class)
* .where().eq("status", Order.NEW)
* .findList();
* ...
* </pre>
*
* }</pre>
*/
public Query<T> where(Expression expression);
@@ -817,15 +932,16 @@ public interface Query<T> extends Serializable {
* ExpressionList. You can use this for adding multiple expressions to the
* where clause.
*
* <pre class="code">
* Query&lt;Order&gt; query = Ebean.createQuery(Order.class, &quot;top&quot;);
* ...
* if (...) {
* query.where()
* .eq(&quot;status&quot;, Order.NEW)
* .ilike(&quot;customer.name&quot;,&quot;rob%&quot;);
* }
* </pre>
* <pre>{@code
*
* List<Order> orders =
* ebeanServer.find(Order.class)
* .where()
* .eq("status", Order.NEW)
* .ilike("customer.name","rob%")
* .findList();
*
* }</pre>
*
* @see Expr
* @return The ExpressionList for adding expressions to.
@@ -843,17 +959,18 @@ public interface Query<T> extends Serializable {
* week. In this case you can use filterMany() to filter the orders.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* List&lt;Customer&gt; list = Ebean
* .find(Customer.class)
* // .fetch(&quot;orders&quot;, new FetchConfig().lazy())
* // .fetch(&quot;orders&quot;, new FetchConfig().query())
* .fetch(&quot;orders&quot;).where().ilike(&quot;name&quot;, &quot;rob%&quot;).filterMany(&quot;orders&quot;)
* .eq(&quot;status&quot;, Order.Status.NEW).gt(
* &quot;orderDate&quot;, lastWeek).findList();
* List<Customer> list =
* ebeanServer.find(Customer.class)
* // .fetch("orders", new FetchConfig().lazy())
* // .fetch("orders", new FetchConfig().query())
* .fetch("orders")
* .where().ilike("name", "rob%")
* .filterMany("orders").eq("status", Order.Status.NEW).gt("orderDate", lastWeek)
* .findList();
*
* </pre>
* }</pre>
*
* <p>
* Please note you have to be careful that you add expressions to the correct
@@ -891,14 +1008,14 @@ public interface Query<T> extends Serializable {
* {@link #setParameter(String, Object)}.
* </p>
*
* <pre class="code">
* Query&lt;ReportOrder&gt; query = Ebean.createQuery(ReportOrder.class);
* ...
* if (...) {
* query.having(&quot;score &gt; :min&quot;);
* query.setParameter(&quot;min&quot;, 1);
* }
* </pre>
* <pre>{@code
*
* List<ReportOrder> query =
* ebeanServer.find(ReportOrder.class)
* .having("score > :min").setParameter("min", 1)
* .findList();
*
* }</pre>
*
* @param addToHavingClause
* the clause to append to the having clause which typically contains
@@ -1030,17 +1147,17 @@ public interface Query<T> extends Serializable {
* If no property is set then the id property is used.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // Assuming sku is unique for products...
*
* Query&lt;Product&gt; query = Ebean.createQuery(Product.class);
*
* // use sku for keys...
* query.setMapKey(&quot;sku&quot;);
*
* Map&lt;?,Product&gt; productMap = query.findMap();
* ...
* </pre>
* Map<?,Product> productMap =
* ebeanServer.find(Product.class)
* // use sku for keys...
* .setMapKey("sku")
* .findMap();
*
* }</pre>
*
* @param mapKey
* the property to use as keys for a map.
@@ -12,19 +12,19 @@ package com.avaje.ebean;
* QueryResultVisitor useful for processing large queries.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* Query&lt;Customer&gt; query = server.find(Customer.class)
* .where().gt(&quot;id&quot;, 0)
* .orderBy(&quot;id&quot;)
* .setMaxRows(2);
* Query<Customer> query = server.find(Customer.class)
* .where().eq("status", Status.NEW)
* .order().asc("id");
*
* query.findVisit((Customer customer) -> {
* query.findEach((Customer customer) -> {
*
* // do something with customer
* System.out.println(&quot;-- visit &quot; + customer);
* System.out.println("-- visit " + customer);
* });
* </pre>
*
* }</pre>
*
* @param <T>
* the type of entity bean being queried.
+64 -25
View File
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
@@ -128,47 +130,93 @@ public interface Transaction extends Closeable {
* Example: batch processing executing every 3 rows
* </p>
*
* <pre class="code">
* String data = &quot;This is a simple test of the batch processing&quot;
* + &quot; mode and the transaction execute batch method&quot;;
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
* + " mode and the transaction execute batch method";
*
* String[] da = data.split(&quot; &quot;);
* String[] da = data.split(" ");
*
* String sql = &quot;{call sp_t3(?,?)}&quot;;
* String sql = "{call sp_t3(?,?)}";
*
* CallableSql cs = new CallableSql(sql);
* cs.registerOut(2, Types.INTEGER);
*
* // (optional) inform eBean this stored procedure
* // inserts into a table called sp_test
* cs.addModification(&quot;sp_test&quot;, true, false, false);
* cs.addModification("sp_test", true, false, false);
*
* Transaction t = Ebean.beginTransaction();
* t.setBatchMode(true);
* t.setBatchSize(3);
* Transaction txn = ebeanServer.beginTransaction();
* txn.setBatchMode(true);
* txn.setBatchSize(3);
* try {
* for (int i = 0; i &lt; da.length;) {
*
* for (int i = 0; i < da.length;) {
* cs.setParameter(1, da[i]);
* Ebean.execute(cs);
* ebeanServer.execute(cs);
* }
*
* // NB: commit implicitly flushes
* Ebean.commitTransaction();
* txn.commit();
*
* } finally {
* Ebean.endTransaction();
* txn.end();
* }
* </pre>
*
* }</pre>
*
*/
public void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
* </p>
*
* @param persistBatchMode the batch mode to use for this transaction
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(com.avaje.ebean.config.PersistBatch)
*/
public void setBatch(PersistBatch persistBatchMode);
/**
* Return the batch mode at the transaction level.
*/
public PersistBatch getBatch();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
* </p>
* <p>
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(com.avaje.ebean.config.PersistBatch)
*/
public void setBatchOnCascade(PersistBatch batchOnCascadeMode);
/**
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
*/
public PersistBatch getBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
public void setBatchSize(int batchSize);
/**
* Return the current batch size.
*/
public int getBatchSize();
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* <p>
@@ -228,20 +276,11 @@ public interface Transaction extends Closeable {
* <li>the batch size is reached</li>
* <li>A query is executed on the same transaction</li>
* <li>UpdateSql or CallableSql are mixed with bean save and delete</li>
* <li>Transaction commit occurs</li>
* </ul>
*/
public void flushBatch() throws PersistenceException, OptimisticLockException;
/**
* Deprecated in favour of {@link #flushBatch()}.
* <p>
* Exactly the same as flushBatch. Deprecated as a name change.
* </p>
*
* @deprecated Please use flushBatch
*/
public void batchFlush() throws PersistenceException, OptimisticLockException;
/**
* Return the underlying Connection object.
* <p>
@@ -1,5 +1,7 @@
package com.avaje.ebean;
import com.avaje.ebean.config.PersistBatch;
import java.util.ArrayList;
/**
@@ -28,12 +30,39 @@ public final class TxScope {
TxIsolation isolation;
PersistBatch batch;
PersistBatch batchOnCascade;
int batchSize;
boolean readOnly;
ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Return true if PersistBatch has been set.
*/
public boolean isBatchSet() {
return batch != null && batch != PersistBatch.INHERIT;
}
/**
* Return true if batch on cascade has been set.
*/
public boolean isBatchOnCascadeSet() {
return batchOnCascade != null && batchOnCascade != PersistBatch.INHERIT;
}
/**
* Return true if batch size has been set.
*/
public boolean isBatchSizeSet() {
return batchSize > 0;
}
/**
* Helper method to create a TxScope with REQUIRES.
*/
@@ -114,6 +143,51 @@ public final class TxScope {
return this;
}
/**
* Return the batch mode.
*/
public PersistBatch getBatch() {
return batch;
}
/**
* Set the batch mode to use.
*/
public TxScope setBatch(PersistBatch batch) {
this.batch = batch;
return this;
}
/**
* Return the batch on cascade mode.
*/
public PersistBatch getBatchOnCascade() {
return batchOnCascade;
}
/**
* Set the batch on cascade mode.
*/
public TxScope setBatchOnCascade(PersistBatch batchOnCascade) {
this.batchOnCascade = batchOnCascade;
return this;
}
/**
* Return the batch size. 0 means use the default value.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Set the batch size to use.
*/
public TxScope setBatchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
/**
* Return if the transaction should be treated as read only.
*/
@@ -20,7 +20,7 @@ import com.avaje.ebean.Query;
* You may also put use the Transient annotation with the Formula annotation.
* The effect of the Transient annotation in this case is that the formula will
* <b>NOT</b> be included in queries by default - you have to explicitly include
* it via {@link Query#select(String)} or {@link Query#join(String, String)}.
* it via {@link Query#select(String)} or {@link Query#fetch(String, String, com.avaje.ebean.FetchConfig)}.
* You may want to do this if the Formula is relatively expensive and only want
* it included in the query when you explicitly state it.
* </p>
@@ -1,103 +1,128 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre class="code">
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* &#064;Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = Ebean.find(Customer.class, 1);
*
* Order order = ...;
* ...
* // save some objects
* Ebean.save(customer);
* Ebean.save(order);
* }
* </pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.avaje.ebean.TxIsolation;
import com.avaje.ebean.TxType;
import com.avaje.ebean.config.PersistBatch;
/**
* Specify transaction scoping for a method.
* <p>
* <b><i> This is only supported if "Enhancement" is used via javaagent, ANT
* task or IDE enhancement plugin etc. </i></b>
* </p>
* <p>
* Note: Currently there are 3 known annotations that perform this role.
* <ul>
* <li>EJB's javax.ejb.TransactionAttribute</li>
* <li>Spring's org.springframework.transaction.annotation.Transactional</li>
* <li>and this one, Ebean's own com.avaje.ebean.annotation.Transactional</li>
* </ul>
* Spring created their one because the EJB annotation does not support features
* such as isolation level and specifying rollbackOn, noRollbackOn exceptions.
* This one exists for Ebean because I agree that the standard one is
* insufficient and don't want to include a dependency on Spring.
* </p>
* <p>
* The default behaviour of EJB (and hence Spring) is to NOT ROLLBACK on checked
* exceptions. I find this very counter-intuitive. Ebean will provide a property
* to set the default behaviour to rollback on any exception and optionally
* change the setting to be consistent with EJB/Spring if people wish to do so.
* </p>
*
* <pre>{@code
*
* // a normal class
* public class MySimpleUserService {
*
* // this method is transactional automatically handling
* // transaction begin, commit and rollback etc
* @Transactional
* public void runInTrans() throws IOException {
*
* // tasks performed within the transaction
* ...
* // find some objects
* Customer cust = ebeanServer.find(Customer.class, 42);
*
* Order order = ...;
* ...
* // save some objects
* ebeanServer.save(customer);
* ebeanServer.save(order);
* }
*
* }</pre>
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
/**
* The type of transaction scoping. Defaults to REQUIRED.
*/
TxType type() default TxType.REQUIRED;
/**
* Persist batch mode for the transaction.
*/
PersistBatch batch() default PersistBatch.INHERIT;
/**
* Persist batch mode for the request if not set on the transaction.
* <p>
* If batch is set to NONE then batchOnCascade can be set to INSERT or ALL
* and then each save(), delete(), insert(), update() request that cascades
* to child beans can use JDBC batch.
* </p>
*/
PersistBatch batchOnCascade() default PersistBatch.INHERIT;
/**
* The batch size to use when using JDBC batch mode.
* <p>
* If unset this defaults to the value set in ServerConfig.
* </p>
*/
int batchSize() default 0;
/**
* The transaction isolation level this transaction should have.
* <p>
* This will only be used if this scope creates the transaction. If the
* transaction has already started then this will currently be ignored (you
* could argue that it should throw an exception).
* </p>
*/
TxIsolation isolation() default TxIsolation.DEFAULT;
/**
* Set this to true if the transaction should be only contain queries.
*/
boolean readOnly() default false;
/**
* The name of the server that you want the transaction to be created from.
* <p>
* If left blank the 'default' server is used.
* </p>
*/
String serverName() default "";
// int timeout() default 0;
/**
* The Throwable's that will explicitly cause a rollback to occur.
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* The Throwable's that will explicitly NOT cause a rollback to occur.
*/
Class<? extends Throwable>[] noRollbackFor() default {};
};
@@ -0,0 +1,52 @@
package com.avaje.ebean.config;
/**
* Defines the mode for JDBC batch processing.
* <p>
* Used both at a per transaction basis and per request basis.
* </p>
*
* @see com.avaje.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
* @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*
* @see com.avaje.ebean.Transaction#setBatch(PersistBatch)
* @see com.avaje.ebean.Transaction#setBatchOnCascade(PersistBatch)
*/
public enum PersistBatch {
/**
* Do not use JDBC Batch mode.
*/
NONE(false),
/**
* Use JDBC Batch mode on Inserts.
*/
INSERT(true),
/**
* Use JDBC Batch mode on Inserts, Updates and Deletes.
*/
ALL(true),
/**
* You should not use this value explicitly. It should only used on the Transactional annotation
* to indicate that the value should inherit from the ServerConfig setting.
*/
INHERIT(false);
boolean forInsert;
PersistBatch(boolean forInsert) {
this.forInsert = forInsert;
}
/**
* Return true if persist cascade should use JDBC batch for inserts.
*/
public boolean forInsert() {
return forInsert;
}
}
@@ -133,7 +133,15 @@ public class ServerConfig {
*/
private int databaseSequenceBatchSize = 20;
private boolean persistBatching;
/**
* Use for transaction scoped batch mode.
*/
private PersistBatch persistBatch = PersistBatch.NONE;
/**
* Use for per request batch mode.
*/
private PersistBatch persistBatchOnCascade = PersistBatch.NONE;
private int persistBatchSize = 20;
@@ -397,36 +405,74 @@ public class ServerConfig {
}
/**
* Returns true if by default JDBC batching is used for persisting or deleting
* Return the PersistBatch mode to use by default at the transaction level.
* <p>
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public PersistBatch getPersistBatch() {
return persistBatch;
}
/**
* Set the JDBC batch mode to use at the transaction level.
* <p>
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public void setPersistBatch(PersistBatch persistBatch) {
this.persistBatch = persistBatch;
}
/**
* Return the JDBC batch mode to use per save(), delete(), insert() or update() request.
* <p>
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public PersistBatch getPersistBatchOnCascade() {
return persistBatchOnCascade;
}
/**
* Set the JDBC batch mode to use per save(), delete(), insert() or update() request.
* <p>
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public void setPersistBatchOnCascade(PersistBatch persistBatchOnCascade) {
this.persistBatchOnCascade = persistBatchOnCascade;
}
/**
* Deprecated, please migrate to using setPersistBatch().
* <p>
* Set to true if you what to use JDBC batching for persisting and deleting
* beans.
* </p>
* <p>
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
* </p>
*/
public boolean isPersistBatching() {
return persistBatching;
}
/**
* Set to true if you what to use JDBC batching for persisting and deleting
* beans.
* <p>
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
* When true this is equivalent to {@code setPersistBatch(PersistBatch.ALL)} or
* when false to {@code setPersistBatch(PersistBatch.NONE)}
* </p>
*/
public void setPersistBatching(boolean persistBatching) {
this.persistBatching = persistBatching;
}
/**
* Use setPersistBatching() instead.
*
* @deprecated
*/
public void setUsePersistBatching(boolean persistBatching) {
this.persistBatching = persistBatching;
this.persistBatch = (persistBatching) ? PersistBatch.ALL : PersistBatch.NONE;
}
/**
@@ -438,11 +484,34 @@ public class ServerConfig {
/**
* Set the batch size used for JDBC batching. If unset this defaults to 20.
* <p>
* You can also set the batch size on the transaction.
* </p>
* @see com.avaje.ebean.Transaction#setBatchSize(int)
*/
public void setPersistBatchSize(int persistBatchSize) {
this.persistBatchSize = persistBatchSize;
}
/**
* Gets the query batch size. This defaults to 100.
*
* @return the query batch size
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Sets the query batch size. This defaults to 100.
*
* @param queryBatchSize
* the new query batch size
*/
public void setQueryBatchSize(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
}
/**
* Return the default batch size for lazy loading of beans and collections.
*/
@@ -450,25 +519,6 @@ public class ServerConfig {
return lazyLoadBatchSize;
}
/**
* Gets the query batch size.
*
* @return the query batch size
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Sets the query batch size.
*
* @param queryBatchSize
* the new query batch size
*/
public void setQueryBatchSize(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
}
/**
* Set the default batch size for lazy loading.
* <p>
@@ -476,7 +526,7 @@ public class ServerConfig {
* invoked by default.
* </p>
* <p>
* The default value is for this is 1 (load 1 bean or collection).
* The default value is for this is 10 (load 10 beans or collections).
* </p>
* <p>
* You can explicitly control the lazy loading batch size for a given join on
@@ -1647,6 +1697,7 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
namingConvention.loadFromProperties(p);
}
@@ -1662,7 +1713,7 @@ public class ServerConfig {
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
namingConvention = createNamingConvention(p);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
@@ -1689,8 +1740,12 @@ public class ServerConfig {
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
boolean batchMode = p.getBoolean("batch.mode", persistBatching);
persistBatching = p.getBoolean("persistBatching", batchMode);
if (p.get("batch.mode") != null || p.get("persistBatching") != null) {
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
}
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
int batchSize = p.getInt("batch.size", persistBatchSize);
persistBatchSize = p.getInt("persistBatchSize", batchSize);
@@ -1721,26 +1776,10 @@ public class ServerConfig {
classes = getClasses(p);
}
private NamingConvention createNamingConvention(PropertiesWrapper properties) {
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
if (nc == null) {
return null;
}
if (nc instanceof AbstractNamingConvention) {
AbstractNamingConvention anc = (AbstractNamingConvention) nc;
String v = properties.get("namingConvention.useForeignKeyPrefix", null);
if (v != null) {
boolean useForeignKeyPrefix = Boolean.valueOf(v);
anc.setUseForeignKeyPrefix(useForeignKeyPrefix);
}
String sequenceFormat = properties.get("namingConvention.sequenceFormat", null);
if (sequenceFormat != null) {
anc.setSequenceFormat(sequenceFormat);
}
}
return nc;
return (nc != null) ? nc : namingConvention;
}
/**
@@ -33,7 +33,7 @@ import com.avaje.ebean.config.ServerConfig;
* </p>
* <p>
* A BeanPersistListener is either found automatically via class path search or
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener<?>)}}.
* can be added programmatically via {@link ServerConfig#add(BeanPersistListener)}}.
* </p>
* @see ServerConfig#add(BeanPersistListener)
*/
+15 -15
View File
@@ -3,8 +3,8 @@
<title>Ebean API</title>
</head>
<body BGCOLOR="#ffffff">
Ebean Object Relational Mapping (start at <a href='com/avaje/ebean/Ebean.html'>Ebean</a>
or <a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a>).
Ebean Object Relational Mapping (start at
<a href='com/avaje/ebean/EbeanServer.html'>EbeanServer</a> or <a href='com/avaje/ebean/Ebean.html'>Ebean</a>).
<h3>Ebean</h3>
@@ -21,15 +21,15 @@ For a full description of the query language refer to <a href="com/avaje/ebean/Q
<h3>
EXAMPLE 1: Simple fetch
</h3>
<pre class="code">
<pre>{@code
// fetch order 10
Order order = Ebean.find(Order.class, 10);
</pre>
}</pre>
<h3>
EXAMPLE 2: Fetch an Object with associations
</h3>
<pre class="code">
<pre>{@code
// fetch Customer 7 including their billing and shipping addresses
Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
@@ -40,19 +40,19 @@ Customer customer = Ebean.find(Customer.class)
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
</pre>
}</pre>
<h3>
EXAMPLE 3: Fetch a list of Objects with associations
</h3>
<pre class="code">
<pre>{@code
// Note: This example shows a "Partial Object".
// For the product objects associated with the
// order details only the product id and name is
// fetched (the product objects are partially populated).
// fetch orders for customer.id = 2
List&lt;Order&gt; orderList = Ebean.find(Order.class);
List<Order> orderList = Ebean.find(Order.class);
.fetch("customer")
.fetch("customer.shippingAddress")
.fetch("details")
@@ -72,17 +72,17 @@ Order order = orderList.get(0);
Customer customer = order.getCustomer();
Address shipAddr = customer.getShippingAddress();
List&lt;OrderDetail&gt; details = order.getDetails();
List<OrderDetail> details = order.getDetails();
OrderDetail detail = details.get(0);
Product product = detail.getProduct();
String productName = product.getName();
</pre>
}</pre>
<h3>
EXAMPLE 4: Create and save an Order
</h3>
<pre class="code">
<pre>{@code
// get a Customer reference so we don't hit the database
Customer custRef = Ebean.getReference(Customer.class, 7);
@@ -107,14 +107,14 @@ orderLines.add(line);
// NB: assumes CascadeType.PERSIST is set on the order lines association
Ebean.save(newOrder);
</pre>
}</pre>
<h3>
EXAMPLE 5: Use another database
</h3>
<pre class="code">
<pre>{@code
// Get access to the Human Resources EbeanServer/Database
EbeanServer hrServer = Ebean.getServer(&quot;HR&quot;);
EbeanServer hrServer = Ebean.getServer("HR");
// fetch contact 3 from the HR database
@@ -125,7 +125,7 @@ contact.setStatus(Contact.Status.INACTIVE);
// save the contact back to the HR database
hrServer.save(contact);
</pre>
}</pre>
</div>
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.config.PersistBatch;
/**
* Used internally to handle the scoping of transactions for methods.
@@ -10,14 +11,14 @@ import com.avaje.ebean.TxScope;
public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private static final int OPCODE_ATHROW = 191;
//private static final int OPCODE_ATHROW = com.avaje.ebean.enhance.asm.Opcodes.ATHROW;
private final SpiTransactionScopeManager scopeMgr;
/**
* The suspended transaction (can be null).
*/
private final SpiTransaction suspendedTransaction;
/**
* The transaction in scope (can be null).
*/
@@ -44,7 +45,13 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
private final ArrayList<Class<? extends Throwable>> rollbackFor;
/**
private PersistBatch restoreBatch;
private PersistBatch restoreBatchOnCascade;
private int restoreBatchSize;
/**
* Flag set when a rollback has occurred.
*/
private boolean rolledBack;
@@ -61,6 +68,24 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
this.noRollbackFor = txScope.getNoRollbackFor();
this.rollbackFor = txScope.getRollbackFor();
if (transaction != null) {
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
restoreBatch = transaction.getBatch();
restoreBatchOnCascade = transaction.getBatchOnCascade();
restoreBatchSize = transaction.getBatchSize();
}
if (txScope.isBatchSet()) {
transaction.setBatch(txScope.getBatch());
}
if (txScope.isBatchOnCascadeSet()) {
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
}
if (txScope.isBatchSizeSet()) {
transaction.setBatchSize(txScope.getBatchSize());
}
}
}
/**
@@ -97,10 +122,22 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
*/
public void onFinally() {
try {
if (!rolledBack && created) {
transaction.commit();
if (!rolledBack) {
if (created) {
transaction.commit();
} else {
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
}
if (restoreBatchOnCascade != null) {
transaction.setBatchOnCascade(restoreBatchOnCascade);
}
if (restoreBatchSize > 0) {
transaction.setBatchSize(restoreBatchSize);
}
}
}
} finally {
if (suspendedTransaction != null){
// put the previously suspended transaction
@@ -5,6 +5,8 @@ import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.persist.BatchControl;
/**
@@ -119,6 +121,11 @@ public interface SpiTransaction extends Transaction {
*/
public int depth(int diff);
/**
* Return the current depth.
*/
public int depth();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
@@ -144,7 +151,7 @@ public interface SpiTransaction extends Transaction {
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
public boolean isBatchThisRequest();
public boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the queue used to batch up persist requests.
@@ -194,4 +201,25 @@ public interface SpiTransaction extends Transaction {
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName);
/**
* Return true if batch mode got escalated for this request (and associated cascades).
*/
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request);
/**
* If batch mode was turned on for the request then flush the batch.
*/
public void flushBatchOnCascade();
/**
* Potentially escalate batch mode on saving or deleting a collection.
*/
public void checkBatchEscalationOnCollection();
/**
* Flush batch if we escalated batch mode on saving or deleting a collection.
*/
public void flushBatchOnCollection();
}
@@ -94,7 +94,7 @@ public class TransactionEvent implements Serializable {
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify(this)) {
if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
@@ -9,7 +9,6 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
@@ -439,20 +438,6 @@ public final class DefaultServer implements SpiEbeanServer {
return serverCacheManager;
}
/**
* Return the Profile Listener.
*/
public AutoFetchManager getProfileListener() {
return autoFetchManager;
}
/**
* Return the Relational query engine.
*/
public RelationalQueryEngine getRelationalQueryEngine() {
return relationalQueryEngine;
}
public void refreshMany(Object parentBean, String propertyName, Transaction t) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName, t);
@@ -1029,15 +1014,10 @@ public final class DefaultServer implements SpiEbeanServer {
public <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> query, Transaction t) {
if (desc.isAutoFetchTunable() && !query.isSqlSelect()) {
// its a tunable query
if (autoFetchManager.tuneQuery(query)) {
// was automatically tuned by Autofetch
} else {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
}
if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoFetchManager.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
}
if (query.selectAllForLazyLoadProperty()) {
@@ -1048,12 +1028,9 @@ public final class DefaultServer implements SpiEbeanServer {
}
}
if (true) {
// if determine cost and no origin for Autofetch
if (query.getParentNode() == null) {
CallStack callStack = createCallStack();
query.setOrigin(callStack);
}
// if determine cost and no origin for Autofetch
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
}
// determine extra joins required to support where clause
@@ -1711,6 +1688,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int saveCount = 0;
while (it.hasNext()) {
@@ -1720,7 +1698,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
wrap.flushBatchOnCollection();
return saveCount;
} catch (RuntimeException e) {
@@ -1804,6 +1782,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int deleteCount = 0;
while (it.hasNext()) {
@@ -1813,7 +1792,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
wrap.flushBatchOnCollection();
return deleteCount;
} catch (RuntimeException e) {
@@ -1864,10 +1843,6 @@ public final class DefaultServer implements SpiEbeanServer {
return execute(update, null);
}
public <T> BeanManager<T> getBeanManager(Class<T> beanClass) {
return beanDescriptorManager.getBeanManager(beanClass);
}
/**
* Return all the BeanDescriptors.
*/
@@ -1875,13 +1850,6 @@ public final class DefaultServer implements SpiEbeanServer {
return beanDescriptorManager.getBeanDescriptorList();
}
public List<MetaBeanInfo> getMetaBeanInfoList() {
List<MetaBeanInfo> list = new ArrayList<MetaBeanInfo>();
list.addAll(getBeanDescriptors());
return list;
}
public void register(BeanPersistController c) {
List<BeanDescriptor<?>> list = beanDescriptorManager.getBeanDescriptorList();
for (int i = 0; i < list.size(); i++) {
@@ -2006,7 +1974,7 @@ public final class DefaultServer implements SpiEbeanServer {
// create the 'interesting' part of the stackTrace
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
System.arraycopy(stackTrace, 0 + startIndex, finalTrace, 0, stackLength);
System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
if (stackLength < 1) {
// this should not really happen
@@ -13,7 +13,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
public enum Type {
DETERMINE, INSERT, UPDATE, DELETE, ORMUPDATE, UPDATESQL, CALLABLESQL
};
}
protected boolean persistCascade;
@@ -24,7 +24,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
protected final PersistExecute persistExecute;
/**
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
*/
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
@@ -41,25 +41,33 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
* Execute the request right now.
*/
public abstract int executeNow();
public PstmtBatch getPstmtBatch() {
return ebeanServer.getPstmtBatch();
}
public boolean isLogSql() {
return transaction.isLogSql();
}
public boolean isLogSummary() {
return transaction.isLogSummary();
}
public PstmtBatch getPstmtBatch() {
return ebeanServer.getPstmtBatch();
}
public boolean isLogSql() {
return transaction.isLogSql();
}
public boolean isLogSummary() {
return transaction.isLogSummary();
}
/**
* Return true if this persist request should use JDBC batch.
*/
public boolean isBatchThisRequest() {
return transaction.isBatchThisRequest(type);
}
/**
* Execute the Callable statement.
* Execute the statement.
*/
public int executeStatement() {
boolean batch = transaction.isBatchThisRequest();
boolean batch = isBatchThisRequest();
int rows;
BatchControl control = transaction.getBatchControl();
@@ -69,15 +77,15 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
} else if (batch) {
// need to create the BatchControl
control = persistExecute.createBatchControl(transaction);
rows = control.executeStatementOrBatch(this, batch);
rows = control.executeStatementOrBatch(this, true);
} else {
rows = executeNow();
}
return rows;
}
public void initTransIfRequired() {
public void initTransIfRequired() {
createImplicitTransIfRequired(false);
persistCascade = transaction.isPersistCascade();
}
@@ -97,6 +97,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private Set<String> updatedProperties;
/**
* Flag set when request is added to JDBC batch.
*/
private boolean batched;
/**
* Flag set when batchOnCascade to avoid using batch on the top bean.
*/
private boolean skipBatchForTopLevel;
/**
* Flag set when batch mode is turned on for a persist cascade.
*/
private boolean batchOnCascadeSet;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
@@ -127,6 +142,54 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.dirty = intercept.isDirty();
}
/**
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
createImplicitTransIfRequired(false);
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
batchOnCascadeSet = !createdTransaction;
}
persistCascade = transaction.isPersistCascade();
}
/**
* If using batch on cascade flush if required.
*/
public void flushBatchOnCascade() {
if (batchOnCascadeSet) {
// we escalated to batch mode for request so flush
transaction.flushBatchOnCascade();
batchOnCascadeSet = false;
}
}
/**
* Return true is this request was added to the JDBC batch.
*/
public boolean isBatched() {
return batched;
}
/**
* Set when request is added to the JDBC batch.
*/
public void setBatched() {
batched = true;
}
public void setSkipBatchForTopLevel() {
skipBatchForTopLevel = true;
}
@Override
public boolean isBatchThisRequest() {
return !skipBatchForTopLevel && super.isBatchThisRequest();
}
/**
* Return true if this is an insert request.
*/
@@ -149,7 +212,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.getDirtyValues();
}
public boolean isNotify(TransactionEvent txnEvent) {
public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify();
return notifyCache || isNotifyPersistListener();
}
@@ -234,7 +297,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (id != null) {
hc += id.hashCode();
}
beanHash = Integer.valueOf(hc);
beanHash = new Integer(hc);
}
return beanHash;
}
@@ -397,7 +460,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public int executeOrQueue() {
boolean batch = transaction.isBatchThisRequest();
boolean batch = isBatchThisRequest();
BatchControl control = transaction.getBatchControl();
if (control != null) {
@@ -405,7 +468,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
if (batch) {
control = persistExecute.createBatchControl(transaction);
return control.executeOrQueue(this, batch);
return control.executeOrQueue(this, true);
} else {
return executeNow();
@@ -438,10 +501,15 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
String m = Message.msg("persist.conc2", "" + rowCount);
throw new OptimisticLockException(m, null, bean);
}
if (type == Type.DELETE) {
postDelete();
}
}
public void postDelete() {
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
private void postDelete() {
// Delete the bean from the PersistenceContent
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
// Delete from cache early even if transaction fails
@@ -457,10 +525,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
if (intercept != null) {
// if bean persisted again then should result in an update
intercept.setLoaded();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
addEvent();
@@ -528,9 +594,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
// check the version property was loaded
BeanProperty prop = beanDescriptor.getVersionProperty();
if (prop != null && intercept.isLoadedProperty(prop.getPropertyIndex())) {
// OK to use version property
} else {
if (prop == null || !intercept.isLoadedProperty(prop.getPropertyIndex())) {
concurrencyMode = ConcurrencyMode.NONE;
}
}
@@ -23,6 +23,16 @@ final class TransWrapper {
wasCreated = created;
}
void batchEscalateOnCollection() {
transaction.checkBatchEscalationOnCollection();
}
void flushBatchOnCollection() {
if (!wasCreated) {
transaction.flushBatchOnCollection();
}
}
void commitIfCreated() {
if (wasCreated){
transaction.commit();
@@ -2,14 +2,14 @@ package com.avaje.ebeaninternal.server.persist;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Controls the batch ordering of persist requests.
@@ -27,24 +27,25 @@ import org.slf4j.LoggerFactory;
*/
public final class BatchControl {
private static final Logger logger = LoggerFactory.getLogger(BatchControl.class);
/**
* Used to sort queue entries by depth.
*/
private static final BatchDepthComparator depthComparator = new BatchDepthComparator();
/**
* The associated transaction.
*/
private final SpiTransaction transaction;
/**
* Controls batching of the PreparedStatements. This should be flushed after
* each 'depth'.
*/
private final BatchedPstmtHolder pstmtHolder = new BatchedPstmtHolder();
/**
* Map of the BatchedBeanHolder objects. They each have a depth and are later
* sorted by their depth to get the execution order.
*/
private final HashMap<String, BatchedBeanHolder> beanHoldMap = new HashMap<String, BatchedBeanHolder>();
private final SpiTransaction transaction;
/**
* The size at which the batch queue will flush. This should be close to the
* number of statements that are batched into a single PreparedStatement. This
@@ -60,17 +61,13 @@ public final class BatchControl {
private boolean batchFlushOnMixed = true;
private final BatchedBeanControl beanControl;
/**
* Create for a given transaction, PersistExecute, default size and
* getGeneratedKeys.
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
*/
public BatchControl(SpiTransaction t, int batchSize, boolean getGenKeys) {
this.transaction = t;
this.batchSize = batchSize;
this.getGeneratedKeys = getGenKeys;
this.beanControl = new BatchedBeanControl(t, this);
transaction.setBatchControl(this);
}
@@ -128,7 +125,7 @@ public final class BatchControl {
* </p>
*/
public int executeStatementOrBatch(PersistRequest request, boolean batch) {
if (!batch || (batchFlushOnMixed && !beanControl.isEmpty())) {
if (!batch || (batchFlushOnMixed && !isBeansEmpty())) {
// flush when mixing beans and updateSql
flush();
}
@@ -161,31 +158,25 @@ public final class BatchControl {
if (!batch) {
return request.executeNow();
}
// get the list we will add this request to
ArrayList<PersistRequest> persistList = beanControl.getPersistList(request);
if (persistList == null) {
// special case where the same bean instance has been added
// to the batch more than once
if (logger.isDebugEnabled()) {
logger.debug("Bean instance already in this batch: " + request.getEntityBean());
}
return -1;
}
if (persistList.size() >= batchSize) {
// flush everything that has been batched
if (addToBatch(request)) {
// flush as the top level has hit the batch size
flush();
// we need to get the persistList again after the
// flush as the flush clears out the bean holders
persistList = beanControl.getPersistList(request);
}
persistList.add(request);
return -1;
}
/**
* Add the request to the batch and return true if we should flush.
*/
private boolean addToBatch(PersistRequestBean<?> request) {
BatchedBeanHolder beanHolder = getBeanHolder(request);
int bufferSize = beanHolder.append(request);
// return true if top level has hit batch size
return bufferSize == batchSize && beanHolder.getOrder() == 100;
}
/**
* Return the actual batch of PreparedStatements.
*/
@@ -197,7 +188,7 @@ public final class BatchControl {
* Return true if the queue is empty.
*/
public boolean isEmpty() {
return (beanControl.isEmpty() && pstmtHolder.isEmpty());
return (isBeansEmpty() && pstmtHolder.isEmpty());
}
/**
@@ -212,27 +203,45 @@ public final class BatchControl {
*/
protected void executeNow(ArrayList<PersistRequest> list) {
for (int i = 0; i < list.size(); i++) {
if (i % batchSize == 0) {
// hit the batch size so flush
flushPstmtHolder();
}
list.get(i).executeNow();
}
flushPstmtHolder();
}
/**
* Flush without resetting the topOrder (maintains the depth info).
*/
public void flush() throws PersistenceException {
flush(false);
}
/**
* Flush with a reset the topOrder (fully empty the batch).
*/
public void flushReset() throws PersistenceException {
flush(true);
}
/**
* execute all the requests currently queued or batched.
*/
public void flush() throws PersistenceException {
private void flush(boolean resetTop) throws PersistenceException {
if (!pstmtHolder.isEmpty()) {
// Flush existing pstmts (updateSql or callableSql)
flushPstmtHolder();
}
if (beanControl.isEmpty()) {
if (isEmpty()) {
// Nothing in queue to flush
return;
}
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = beanControl.getArray();
BatchedBeanHolder[] bsArray = getBeanHolderArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
@@ -240,11 +249,55 @@ public final class BatchControl {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (int i = 0; i < bsArray.length; i++) {
BatchedBeanHolder bs = bsArray[i];
bs.executeNow();
// flush all the batched Pstmts
flushPstmtHolder();
bsArray[i].executeNow();
}
if (resetTop) {
beanHoldMap.clear();
}
}
/**
* Return an entry for the given type description. The type description is
* typically the bean class name (or table name for MapBeans).
*/
private BatchedBeanHolder getBeanHolder(PersistRequestBean<?> request) {
BeanDescriptor<?> beanDescriptor = request.getBeanDescriptor();
BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName());
if (batchBeanHolder == null) {
int relativeDepth = transaction.depth();
if (relativeDepth == 0 && !beanHoldMap.isEmpty()) {
// flush and reset the batch as we are changing the type of our top level
// bean so just keep it simple and flush and reset the top
flushReset();
}
batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, 100 + relativeDepth);
beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder);
}
return batchBeanHolder;
}
/**
* Return true if this holds no persist requests.
*/
private boolean isBeansEmpty() {
if (beanHoldMap.isEmpty()) {
return true;
}
for (BatchedBeanHolder beanHolder : beanHoldMap.values()) {
if (!beanHolder.isEmpty()) {
return false;
}
}
return true;
}
/**
* Return the BatchedBeanHolder's ready for sorting and executing.
*/
private BatchedBeanHolder[] getBeanHolderArray() {
return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]);
}
}
@@ -1,79 +0,0 @@
package com.avaje.ebeaninternal.server.persist;
import java.util.ArrayList;
import java.util.HashMap;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Holds all the batched beans.
* <p>
* The beans are held here which delays the binding to a PreparedStatement. This
* 'delayed' binding is required as the beans need to be bound and executed in
* the correct order (according to the depth).
* </p>
*/
public class BatchedBeanControl {
/**
* Map of the BatchedBeanHolder objects. They each have a depth and are later
* sorted by their depth to get the execution order.
*/
private final HashMap<String, BatchedBeanHolder> beanHoldMap = new HashMap<String, BatchedBeanHolder>();
private final SpiTransaction transaction;
private final BatchControl batchControl;
private int topOrder;
public BatchedBeanControl(SpiTransaction t, BatchControl batchControl) {
this.transaction = t;
this.batchControl = batchControl;
}
public ArrayList<PersistRequest> getPersistList(PersistRequestBean<?> request) {
return getBeanHolder(request).getList(request);
}
/**
* Return an entry for the given type description. The type description is
* typically the bean class name (or table name for MapBeans).
*/
private BatchedBeanHolder getBeanHolder(PersistRequestBean<?> request) {
BeanDescriptor<?> beanDescriptor = request.getBeanDescriptor();
BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName());
if (batchBeanHolder == null) {
int relativeDepth = transaction.depth(0);
if (relativeDepth == 0){
topOrder++;
}
int stmtOrder = topOrder*100 + relativeDepth;
batchBeanHolder = new BatchedBeanHolder(batchControl, beanDescriptor, stmtOrder);
beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder);
}
return batchBeanHolder;
}
/**
* Return true if this holds no persist requests.
*/
public boolean isEmpty() {
return beanHoldMap.isEmpty();
}
/**
* Return the BatchedBeanHolder's ready for sorting and executing.
*/
public BatchedBeanHolder[] getArray() {
BatchedBeanHolder[] bsArray = new BatchedBeanHolder[beanHoldMap.size()];
beanHoldMap.values().toArray(bsArray);
return bsArray;
}
}
@@ -8,10 +8,10 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Holds lists of persist requests for beans of a given typeDescription.
* Holds lists of persist requests for beans of a given type.
* <p>
* This is used to delay the actual binding of the bean to PreparedStatements.
* The reason is that don't have all the bind values yet in the case of inserts
* The reason is that we don't have all the bind values yet in the case of inserts
* with getGeneratedKeys.
* </p>
* <p>
@@ -21,118 +21,139 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public class BatchedBeanHolder {
/**
* The owning queue.
*/
private final BatchControl control;
/**
* The owning queue.
*/
private final BatchControl control;
private final String shortDesc;
private final String shortDesc;
/**
* The 'depth' which is used to determine the execution order.
*/
private final int order;
/**
* The 'depth' which is used to determine the execution order.
*/
private final int order;
/**
* The list of bean insert requests.
*/
private ArrayList<PersistRequest> inserts;
/**
* The list of bean insert requests.
*/
private ArrayList<PersistRequest> inserts;
/**
* The list of bean update requests.
*/
private ArrayList<PersistRequest> updates;
/**
* The list of bean update requests.
*/
private ArrayList<PersistRequest> updates;
/**
* The list of bean delete requests.
*/
private ArrayList<PersistRequest> deletes;
/**
* The list of bean delete requests.
*/
private ArrayList<PersistRequest> deletes;
private HashSet<Integer> beanHashCodes = new HashSet<Integer>();
/**
* Create a new entry with a given type and depth.
*/
public BatchedBeanHolder(BatchControl control, BeanDescriptor<?> beanDescriptor, int order) {
this.control = control;
this.shortDesc = beanDescriptor.getName() + ":" + order;
this.order = order;
}
private HashSet<Integer> beanHashCodes = new HashSet<Integer>();
/**
* Return the depth.
*/
public int getOrder() {
return order;
}
/**
* Execute all the persist requests in this entry.
* <p>
* This will Batch all the similar requests into one or more BatchStatements
* and then execute them.
* </p>
*/
public void executeNow() {
// process the requests. Creates one or more PreparedStatements
// with binding addBatch() for each request.
/**
* Create a new entry with a given type and depth.
*/
public BatchedBeanHolder(BatchControl control, BeanDescriptor<?> beanDescriptor, int order) {
this.control = control;
this.shortDesc = beanDescriptor.getName() + ":" + order;
this.order = order;
}
// Note updates and deletes can result in many PreparedStatements
// if their where clauses differ via use of IS NOT NULL.
if (inserts != null && !inserts.isEmpty()) {
control.executeNow(inserts);
inserts.clear();
}
if (updates != null && !updates.isEmpty()) {
control.executeNow(updates);
updates.clear();
}
if (deletes != null && !deletes.isEmpty()) {
control.executeNow(deletes);
deletes.clear();
}
beanHashCodes.clear();
}
/**
* Return the depth.
*/
public int getOrder() {
return order;
}
public String toString() {
return shortDesc;
}
/**
* Execute all the persist requests in this entry.
* <p>
* This will Batch all the similar requests into one or more BatchStatements
* and then execute them.
* </p>
*/
public void executeNow() {
// process the requests. Creates one or more PreparedStatements
// with binding addBatch() for each request.
// Note updates and deletes can result in many PreparedStatements
// if their where clauses differ via use of IS NOT NULL.
if (inserts != null && !inserts.isEmpty()) {
control.executeNow(inserts);
inserts.clear();
}
if (updates != null && !updates.isEmpty()) {
control.executeNow(updates);
updates.clear();
}
if (deletes != null && !deletes.isEmpty()) {
control.executeNow(deletes);
deletes.clear();
}
beanHashCodes.clear();
}
/**
* Return the list for the typeCode.
*/
public ArrayList<PersistRequest> getList(PersistRequestBean<?> request) {
Integer objHashCode = Integer.valueOf(System.identityHashCode(request.getEntityBean()));
if (!beanHashCodes.add(objHashCode)) {
// special case where the same bean instance has already been
// added to the batch (doesn't really occur with non-batching
// as the bean gets changed from dirty to loaded earlier)
return null;
}
switch (request.getType()) {
case INSERT:
if (inserts == null) {
inserts = new ArrayList<PersistRequest>();
}
return inserts;
public String toString() {
StringBuilder sb = new StringBuilder(shortDesc.length()+18);
sb.append(shortDesc);
if (inserts != null) {
sb.append(" i:").append(inserts.size());
}
if (updates != null) {
sb.append(" u:").append(updates.size());
}
if (deletes != null) {
sb.append(" d:").append(deletes.size());
}
return sb.toString();
}
case UPDATE:
if (updates == null) {
updates = new ArrayList<PersistRequest>();
}
return updates;
/**
* Add the request to the appropriate persist list.
*/
public int append(PersistRequestBean<?> request) {
case DELETE:
if (deletes == null) {
deletes = new ArrayList<PersistRequest>();
}
return deletes;
Integer objHashCode = new Integer(System.identityHashCode(request.getEntityBean()));
if (!beanHashCodes.add(objHashCode)) {
// special case where the same bean instance has already been
// added to the batch (doesn't really occur with non-batching
// as the bean gets changed from dirty to loaded earlier)
return 0;
}
default:
throw new RuntimeException("Invalid type code " + request.getType());
}
}
request.setBatched();
switch (request.getType()) {
case INSERT:
if (inserts == null) {
inserts = new ArrayList<PersistRequest>();
}
inserts.add(request);
return inserts.size();
case UPDATE:
if (updates == null) {
updates = new ArrayList<PersistRequest>();
}
updates.add(request);
return updates.size();
case DELETE:
if (deletes == null) {
deletes = new ArrayList<PersistRequest>();
}
deletes.add(request);
return deletes.size();
default:
throw new RuntimeException("Invalid type code " + request.getType());
}
}
/**
* Return true if this is empty containing no batched beans.
*/
public boolean isEmpty() {
return beanHashCodes.isEmpty();
}
}
@@ -168,20 +168,19 @@ public final class DefaultPersister implements Persister {
PersistRequestBean<?> req = createRequest(entityBean, t, null, PersistRequest.Type.UPDATE);
req.setDeleteMissingChildren(deleteMissingChildren);
try {
req.initTransIfRequired();
req.initTransIfRequiredWithBatchCascade();
if (req.isReference()) {
// its a reference so see if there are manys to save...
if (req.isPersistCascade()) {
saveAssocMany(false, req, false);
}
req.checkUpdatedManysOnly();
} else {
update(req);
}
req.commitTransIfRequired();
req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -208,9 +207,10 @@ public final class DefaultPersister implements Persister {
PersistRequestBean<?> req = createRequest(bean, t, null, PersistRequest.Type.INSERT);
try {
req.initTransIfRequired();
req.initTransIfRequiredWithBatchCascade();
insert(req);
req.commitTransIfRequired();
req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -323,9 +323,10 @@ public final class DefaultPersister implements Persister {
}
try {
req.initTransIfRequired();
req.initTransIfRequiredWithBatchCascade();
delete(req);
req.commitTransIfRequired();
req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -558,9 +559,7 @@ public final class DefaultPersister implements Persister {
if (request.isLoadedProperty(prop)) {
EntityBean detailBean = prop.getValueAsEntityBean(parentBean);
if (detailBean != null) {
if (prop.isSaveRecurseSkippable(detailBean)) {
// skip saving this bean
} else {
if (!prop.isSaveRecurseSkippable(detailBean)) {
t.depth(+1);
prop.setParentBeanToChild(parentBean, detailBean);
saveRecurse(detailBean, t, parentBean, insertMode);
@@ -748,11 +747,11 @@ public final class DefaultPersister implements Persister {
// set it to the appropriate property on the
// detail bean before we save it
boolean isMap = ManyType.JAVA_MAP.equals(prop.getManyType());
EntityBean parentBean = (EntityBean)saveMany.getParentBean();
EntityBean parentBean = saveMany.getParentBean();
Object mapKeyValue = null;
boolean saveSkippable = prop.isSaveRecurseSkippable();
boolean skipSavingThisBean = false;
boolean skipSavingThisBean;
for (Object detailBean : collection) {
if (isMap) {
@@ -762,11 +761,7 @@ public final class DefaultPersister implements Persister {
detailBean = entry.getValue();
}
if (detailBean instanceof EntityBean == false) {
skipSavingThisBean = true;
logger.debug("Skip non entity bean");
} else {
if (detailBean instanceof EntityBean) {
EntityBean detail = (EntityBean)detailBean;
EntityBeanIntercept ebi = detail._ebean_getIntercept();
if (prop.isManyToMany()) {
@@ -787,14 +782,7 @@ public final class DefaultPersister implements Persister {
}
}
if (skipSavingThisBean) {
// unmodified bean that does not recurse its save
// so we can skip the save for this bean.
// Reset skipSavingThisBean for the next detailBean
skipSavingThisBean = false;
} else {
// normal save recurse
if (!skipSavingThisBean) {
saveRecurse(detail, t, parentBean, insertMode);
}
if (detailIds != null) {
@@ -879,9 +867,6 @@ public final class DefaultPersister implements Persister {
}
SpiTransaction t = saveManyPropRequest.getTransaction();
Collection<?> additions = null;
Collection<?> deletions = null;
boolean vanillaCollection = !(value instanceof BeanCollection<?>);
if (vanillaCollection || deleteMissingChildren) {
@@ -890,6 +875,9 @@ public final class DefaultPersister implements Persister {
deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t);
}
Collection<?> deletions = null;
Collection<?> additions;
if (saveManyPropRequest.isInsertedParent() || vanillaCollection || deleteMissingChildren) {
// treat everything in the list/set/map as an intersection addition
if (value instanceof Map<?, ?>) {
@@ -1098,30 +1086,24 @@ public final class DefaultPersister implements Persister {
// imported ones with save cascade
BeanPropertyAssocOne<?>[] ones = desc.propertiesOneImportedSave();
for (int i = 0; i < ones.length; i++) {
BeanPropertyAssocOne<?> prop = ones[i];
for (int i = 0; i < ones.length; i++) {
BeanPropertyAssocOne<?> prop = ones[i];
// check for partial objects
if (request.isLoadedProperty(prop)) {
EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
if (detailBean != null) {
if (prop.isReference(detailBean)) {
// skip saving a reference
} else if (request.isParent(detailBean)) {
// skip saving the parent as already saved
} else if (prop.isSaveRecurseSkippable(detailBean)) {
// we can skip saving this bean
} else {
SpiTransaction t = request.getTransaction();
t.depth(-1);
saveRecurse(detailBean, t, null, insertMode);
t.depth(+1);
}
}
}
}
}
// check for partial objects
if (request.isLoadedProperty(prop)) {
EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
if (detailBean != null
&& !prop.isSaveRecurseSkippable(detailBean)
&& !prop.isReference(detailBean)
&& !request.isParent(detailBean)) {
SpiTransaction t = request.getTransaction();
t.depth(-1);
saveRecurse(detailBean, t, null, insertMode);
t.depth(+1);
}
}
}
}
/**
* Support for loading any Imported Associated One properties that are not
@@ -1156,10 +1138,7 @@ public final class DefaultPersister implements Persister {
for (int i = 0; i < ones.length; i++) {
BeanPropertyAssocOne<?> prop = ones[i];
if (!request.isLoadedProperty(prop)) {
// handled by DeleteUnloadedForeignKeys that was built
// via getDeleteUnloadedForeignKeys();
} else {
if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(request.getEntityBean());
if (detailBean != null) {
EntityBean detail = (EntityBean)detailBean;
@@ -20,100 +20,92 @@ import org.slf4j.LoggerFactory;
*/
public class ExeCallableSql {
private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class);
private final Binder binder;
private final PstmtFactory pstmtFactory;
public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) {
this.binder = binder;
// no batch support for CallableStatement in Oracle anyway
this.pstmtFactory = new PstmtFactory(null);
}
/**
* execute the CallableSql requests.
*/
public int execute(PersistRequestCallableSql request) {
private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class);
SpiTransaction t = request.getTransaction();
boolean batchThisRequest = t.isBatchThisRequest();
CallableStatement cstmt = null;
private final Binder binder;
private final PstmtFactory pstmtFactory;
public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) {
this.binder = binder;
// no batch support for CallableStatement in Oracle anyway
this.pstmtFactory = new PstmtFactory(null);
}
/**
* execute the CallableSql requests.
*/
public int execute(PersistRequestCallableSql request) {
boolean batchThisRequest = request.isBatchThisRequest();
CallableStatement cstmt = null;
try {
cstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest) {
cstmt.addBatch();
// return -1 to indicate batch mode
return -1;
} else {
// handles executeOverride() and also
// reading of registered OUT parameters
int rowCount = request.executeUpdate();
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (!batchThisRequest && cstmt != null) {
try {
cstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest){
cstmt.addBatch();
// return -1 to indicate batch mode
return -1;
} else {
// handles executeOverride() and also
// reading of registered OUT parameters
int rowCount = request.executeUpdate();
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (!batchThisRequest && cstmt != null) {
try {
cstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
cstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
}
private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {
SpiCallableSql callableSql = request.getCallableSql();
SpiTransaction t = request.getTransaction();
String sql = callableSql.getSql();
BindParams bindParams = callableSql.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
boolean logSql = request.isLogSql();
CallableStatement cstmt;
if (batchThisRequest){
cstmt = pstmtFactory.getCstmt(t, logSql, sql, request);
} else {
if (logSql){
t.logSql(sql);
}
cstmt = pstmtFactory.getCstmt(t, sql);
}
if (callableSql.getTimeout() > 0){
cstmt.setQueryTimeout(callableSql.getTimeout());
}
String bindLog = null;
if (!bindParams.isEmpty()){
bindLog = binder.bind(bindParams, new DataBind(cstmt));
}
request.setBindLog(bindLog);
// required to read OUT params later
request.setBound(bindParams, cstmt);
return cstmt;
}
private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {
SpiCallableSql callableSql = request.getCallableSql();
SpiTransaction t = request.getTransaction();
String sql = callableSql.getSql();
BindParams bindParams = callableSql.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
boolean logSql = request.isLogSql();
CallableStatement cstmt;
if (batchThisRequest) {
cstmt = pstmtFactory.getCstmt(t, logSql, sql, request);
} else {
if (logSql) {
t.logSql(sql);
}
cstmt = pstmtFactory.getCstmt(t, sql);
}
if (callableSql.getTimeout() > 0) {
cstmt.setQueryTimeout(callableSql.getTimeout());
}
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(cstmt));
}
request.setBindLog(bindLog);
// required to read OUT params later
request.setBound(bindParams, cstmt);
return cstmt;
}
}
@@ -21,121 +21,109 @@ import org.slf4j.LoggerFactory;
*/
public class ExeOrmUpdate {
private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class);
private final Binder binder;
private final PstmtFactory pstmtFactory;
/**
* Create with a given binder.
*/
public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) {
this.pstmtFactory = new PstmtFactory(pstmtBatch);
this.binder = binder;
}
/**
* Execute the orm update request.
*/
public int execute(PersistRequestOrmUpdate request) {
private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class);
SpiTransaction t = request.getTransaction();
boolean batchThisRequest = t.isBatchThisRequest();
PreparedStatement pstmt = null;
private final Binder binder;
private final PstmtFactory pstmtFactory;
/**
* Create with a given binder.
*/
public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) {
this.pstmtFactory = new PstmtFactory(pstmtBatch);
this.binder = binder;
}
/**
* Execute the orm update request.
*/
public int execute(PersistRequestOrmUpdate request) {
boolean batchThisRequest = request.isBatchThisRequest();
PreparedStatement pstmt = null;
try {
pstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest) {
PstmtBatch pstmtBatch = request.getPstmtBatch();
if (pstmtBatch != null) {
pstmtBatch.addBatch(pstmt);
} else {
pstmt.addBatch();
}
// return -1 to indicate batch mode
return -1;
} else {
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
if (ormUpdate.getTimeout() > 0) {
pstmt.setQueryTimeout(ormUpdate.getTimeout());
}
int rowCount = pstmt.executeUpdate();
request.checkRowCount(rowCount);
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
throw new PersistenceException("Error executing: " + request.getOrmUpdate().getGeneratedSql(), ex);
} finally {
if (!batchThisRequest && pstmt != null) {
try {
pstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest){
PstmtBatch pstmtBatch = request.getPstmtBatch();
if (pstmtBatch != null){
pstmtBatch.addBatch(pstmt);
} else {
pstmt.addBatch();
}
// return -1 to indicate batch mode
return -1;
} else {
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
if (ormUpdate.getTimeout() > 0){
pstmt.setQueryTimeout(ormUpdate.getTimeout());
}
int rowCount = pstmt.executeUpdate();
request.checkRowCount(rowCount);
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
String msg = "Error executing: "+ormUpdate.getGeneratedSql();
throw new PersistenceException(msg, ex);
} finally {
if (!batchThisRequest && pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
pstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
}
/**
* Convert bean and property names to db table and columns.
*/
private String translate(PersistRequestOrmUpdate request, String sql) {
BeanDescriptor<?> descriptor = request.getBeanDescriptor();
return descriptor.convertOrmUpdateToSql(sql);
}
private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException {
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
SpiTransaction t = request.getTransaction();
String sql = ormUpdate.getUpdateStatement();
// convert bean and property names to table and
// column names if required
sql = translate(request, sql);
BindParams bindParams = ormUpdate.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
ormUpdate.setGeneratedSql(sql);
boolean logSql = request.isLogSql();
PreparedStatement pstmt;
if (batchThisRequest){
pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
} else {
if (logSql){
t.logSql(sql);
}
pstmt = pstmtFactory.getPstmt(t, sql);
}
String bindLog = null;
if (!bindParams.isEmpty()){
bindLog = binder.bind(bindParams, new DataBind(pstmt));
}
request.setBindLog(bindLog);
return pstmt;
}
/**
* Convert bean and property names to db table and columns.
*/
private String translate(PersistRequestOrmUpdate request, String sql) {
BeanDescriptor<?> descriptor = request.getBeanDescriptor();
return descriptor.convertOrmUpdateToSql(sql);
}
private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException {
SpiUpdate<?> ormUpdate = request.getOrmUpdate();
SpiTransaction t = request.getTransaction();
String sql = ormUpdate.getUpdateStatement();
// convert bean and property names to table and
// column names if required
sql = translate(request, sql);
BindParams bindParams = ormUpdate.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
ormUpdate.setGeneratedSql(sql);
boolean logSql = request.isLogSql();
PreparedStatement pstmt;
if (batchThisRequest) {
pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
} else {
if (logSql) {
t.logSql(sql);
}
pstmt = pstmtFactory.getPstmt(t, sql);
}
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(pstmt));
}
request.setBindLog(bindLog);
return pstmt;
}
}
@@ -1,10 +1,5 @@
package com.avaje.ebeaninternal.server.persist;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.SpiSqlUpdate;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -16,185 +11,180 @@ import com.avaje.ebeaninternal.server.util.BindParamsParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Executes the UpdateSql requests.
*/
public class ExeUpdateSql {
private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class);
private final Binder binder;
private final PstmtFactory pstmtFactory;
private final PstmtBatch pstmtBatch;
//TODO: get defaultBatchSize
private int defaultBatchSize = 20;
/**
* Create with a given binder.
*/
public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) {
this.binder = binder;
this.pstmtBatch = pstmtBatch;
this.pstmtFactory = new PstmtFactory(pstmtBatch);
}
/**
* Execute the UpdateSql request.
*/
public int execute(PersistRequestUpdateSql request) {
private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class);
SpiTransaction t = request.getTransaction();
boolean batchThisRequest = t.isBatchThisRequest();
PreparedStatement pstmt = null;
private final Binder binder;
private final PstmtFactory pstmtFactory;
private final PstmtBatch pstmtBatch;
private int defaultBatchSize = 20;
/**
* Create with a given binder.
*/
public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) {
this.binder = binder;
this.pstmtBatch = pstmtBatch;
this.pstmtFactory = new PstmtFactory(pstmtBatch);
}
/**
* Execute the UpdateSql request.
*/
public int execute(PersistRequestUpdateSql request) {
boolean batchThisRequest = request.isBatchThisRequest();
PreparedStatement pstmt = null;
try {
pstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest) {
if (pstmtBatch != null) {
pstmtBatch.addBatch(pstmt);
} else {
pstmt.addBatch();
}
// return -1 to indicate batch mode
return -1;
} else {
int rowCount = pstmt.executeUpdate();
request.checkRowCount(rowCount);
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (!batchThisRequest && pstmt != null) {
try {
pstmt = bindStmt(request, batchThisRequest);
if (batchThisRequest){
if (pstmtBatch != null){
pstmtBatch.addBatch(pstmt);
} else {
pstmt.addBatch();
}
// return -1 to indicate batch mode
return -1;
} else {
int rowCount = pstmt.executeUpdate();
request.checkRowCount(rowCount);
request.postExecute();
return rowCount;
}
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (!batchThisRequest && pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
pstmt.close();
} catch (SQLException e) {
logger.error(null, e);
}
}
}
private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
SpiSqlUpdate updateSql = request.getUpdateSql();
SpiTransaction t = request.getTransaction();
String sql = updateSql.getSql();
BindParams bindParams = updateSql.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
updateSql.setGeneratedSql(sql);
boolean logSql = request.isLogSql();
PreparedStatement pstmt;
if (batchThisRequest){
pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
if (pstmtBatch != null){
// oracle specific JDBC setting batch size ahead of time
int batchSize = t.getBatchSize();
if (batchSize < 1){
batchSize = defaultBatchSize;
}
pstmtBatch.setBatchSize(pstmt, batchSize);
}
} else {
if (logSql){
t.logSql(sql);
}
pstmt = pstmtFactory.getPstmt(t, sql);
}
if (updateSql.getTimeout() > 0){
pstmt.setQueryTimeout(updateSql.getTimeout());
}
String bindLog = null;
if (!bindParams.isEmpty()){
bindLog = binder.bind(bindParams, new DataBind(pstmt));
}
private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
SpiSqlUpdate updateSql = request.getUpdateSql();
SpiTransaction t = request.getTransaction();
String sql = updateSql.getSql();
BindParams bindParams = updateSql.getBindParams();
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
updateSql.setGeneratedSql(sql);
boolean logSql = request.isLogSql();
PreparedStatement pstmt;
if (batchThisRequest) {
pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
if (pstmtBatch != null) {
// oracle specific JDBC setting batch size ahead of time
int batchSize = t.getBatchSize();
if (batchSize < 1) {
batchSize = defaultBatchSize;
}
request.setBindLog(bindLog);
// derive the statement type (for TransactionEvent)
parseUpdate(sql, request);
return pstmt;
pstmtBatch.setBatchSize(pstmt, batchSize);
}
} else {
if (logSql) {
t.logSql(sql);
}
pstmt = pstmtFactory.getPstmt(t, sql);
}
private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) {
if (word1.equalsIgnoreCase("UPDATE")) {
request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql");
} else if (word1.equalsIgnoreCase("DELETE")) {
request.setType(SqlType.SQL_DELETE, word3, "DeleteSql");
} else if (word1.equalsIgnoreCase("INSERT")) {
request.setType(SqlType.SQL_INSERT, word3, "InsertSql");
} else {
request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql");
}
if (updateSql.getTimeout() > 0) {
pstmt.setQueryTimeout(updateSql.getTimeout());
}
private void parseUpdate(String sql, PersistRequestUpdateSql request) {
int start = ltrim(sql);
int[] pos = new int[3];
int spaceCount = 0;
int len = sql.length();
for (int i = start; i < len; i++) {
char c = sql.charAt(i);
if (Character.isWhitespace(c)) {
pos[spaceCount] = i;
spaceCount++;
if (spaceCount > 2){
break;
}
}
}
String firstWord = sql.substring(0, pos[0]);
String secWord = sql.substring(pos[0]+1, pos[1]);
String thirdWord;
if (pos[2] == 0){
// there is nothing after the table name
thirdWord = sql.substring(pos[1]+1);
} else {
thirdWord = sql.substring(pos[1]+1, pos[2]);
}
determineType(firstWord, secWord, thirdWord, request);
String bindLog = null;
if (!bindParams.isEmpty()) {
bindLog = binder.bind(bindParams, new DataBind(pstmt));
}
private int ltrim(String s) {
int len = s.length();
int i = 0;
for (i = 0; i < len; i++) {
if (!Character.isWhitespace(s.charAt(i))) {
return i;
}
}
return 0;
request.setBindLog(bindLog);
// derive the statement type (for TransactionEvent)
parseUpdate(sql, request);
return pstmt;
}
private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) {
if (word1.equalsIgnoreCase("UPDATE")) {
request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql");
} else if (word1.equalsIgnoreCase("DELETE")) {
request.setType(SqlType.SQL_DELETE, word3, "DeleteSql");
} else if (word1.equalsIgnoreCase("INSERT")) {
request.setType(SqlType.SQL_INSERT, word3, "InsertSql");
} else {
request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql");
}
}
private void parseUpdate(String sql, PersistRequestUpdateSql request) {
int start = leadingTrim(sql);
int[] pos = new int[3];
int spaceCount = 0;
int len = sql.length();
for (int i = start; i < len; i++) {
char c = sql.charAt(i);
if (Character.isWhitespace(c)) {
pos[spaceCount] = i;
spaceCount++;
if (spaceCount > 2) {
break;
}
}
}
String firstWord = sql.substring(0, pos[0]);
String secWord = sql.substring(pos[0] + 1, pos[1]);
String thirdWord;
if (pos[2] == 0) {
// there is nothing after the table name
thirdWord = sql.substring(pos[1] + 1);
} else {
thirdWord = sql.substring(pos[1] + 1, pos[2]);
}
determineType(firstWord, secWord, thirdWord, request);
}
private int leadingTrim(String s) {
int len = s.length();
int i;
for (i = 0; i < len; i++) {
if (!Character.isWhitespace(s.charAt(i))) {
return i;
}
}
return 0;
}
}
@@ -28,20 +28,16 @@ public class DeleteHandler extends DmlHandler {
public void bind() throws SQLException {
sql = meta.getSql(persistRequest);
SpiTransaction t = persistRequest.getTransaction();
boolean isBatch = t.isBatchThisRequest();
PreparedStatement pstmt;
if (isBatch) {
if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, false);
} else {
pstmt = getPstmt(t, sql, false);
}
dataBind = new DataBind(pstmt);
meta.bind(persistRequest, this);
logSql(sql);
}
@@ -51,9 +47,6 @@ public class DeleteHandler extends DmlHandler {
public void execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
// Deletes the bean from the PersistenceContext
persistRequest.postDelete();
}
public void registerDerivedRelationship(DerivedRelationshipData assocBean) {
@@ -69,35 +69,28 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute request taking batching into account.
*/
private void execute(PersistRequest request, PersistHandler handler) {
SpiTransaction trans = request.getTransaction();
boolean batchThisRequest = trans.isBatchThisRequest();
private void execute(PersistRequestBean<?> request, PersistHandler handler) {
boolean batched = request.isBatched();
try {
handler.bind();
if (batchThisRequest) {
if (batched) {
handler.addBatch();
} else {
// immediate insert
handler.execute();
}
} catch (SQLException e) {
// log the error to the transaction log
String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n ");
String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]";
if (request.getTransaction().isLogSummary()) {
request.getTransaction().logSummary(msg);
}
// log the error to the transaction log
String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n ");
String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]";
if (request.getTransaction().isLogSummary()) {
request.getTransaction().logSummary(msg);
}
throw new PersistenceException(msg, e);
} finally {
if (!batchThisRequest && handler != null) {
if (!batched && handler != null) {
try {
handler.close();
} catch (SQLException e) {
@@ -88,13 +88,12 @@ public class InsertHandler extends DmlHandler {
}
SpiTransaction t = persistRequest.getTransaction();
boolean isBatch = t.isBatchThisRequest();
// get the appropriate sql
sql = meta.getSql(withId);
PreparedStatement pstmt;
if (isBatch) {
if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, useGeneratedKeys);
} else {
pstmt = getPstmt(t, sql, useGeneratedKeys);
@@ -137,9 +136,7 @@ public class InsertHandler extends DmlHandler {
}
checkRowCount(rc);
//setAdditionalProperties();
executeDerivedRelationships();
persistRequest.postInsert();
}
@@ -40,10 +40,9 @@ public class UpdateHandler extends DmlHandler {
sql = updatePlan.getSql();
SpiTransaction t = persistRequest.getTransaction();
boolean isBatch = t.isBatchThisRequest();
PreparedStatement pstmt;
if (isBatch) {
if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, false);
} else {
pstmt = getPstmt(t, sql, false);
@@ -28,6 +28,8 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
private final Monitor monitor = new Monitor();
private int foregroundTotalRowCount = -1;
private Future<Integer> futureRowCount;
private List<T> list;
@@ -51,13 +53,12 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
return futureRowCount;
}
}
public List<T> getList() {
synchronized (monitor) {
if (list == null) {
query.setFirstRow(pageIndex * pageSize);
query.setMaxRows(pageSize);
list = server.findList(query, null);
}
return list;
@@ -75,10 +76,21 @@ public class LimitOffsetPagedList<T> implements PagedList<T> {
}
public int getTotalRowCount() {
try {
return getFutureRowCount().get();
} catch (Exception e) {
throw new PersistenceException(e);
synchronized (monitor) {
if (futureRowCount != null) {
try {
// background query already initiated so get it with a wait
return futureRowCount.get();
} catch (Exception e) {
throw new PersistenceException(e);
}
}
// already fetched?
if (foregroundTotalRowCount > -1) return foregroundTotalRowCount;
// just using foreground thread
foregroundTotalRowCount = server.findRowCount(query, null);
return foregroundTotalRowCount;
}
}
@@ -73,7 +73,7 @@ public class WriteJson {
return new WriteBean(desc, explicitAllProps, currentIncludeProps, bean);
}
public class WriteBean {
public static class WriteBean {
final boolean explicitAllProps;
final Set<String> currentIncludeProps;
@@ -1,29 +1,25 @@
package com.avaje.ebeaninternal.server.transaction;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import com.avaje.ebean.TransactionCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEvent;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.transaction.TransactionManager.OnQueryOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
/**
* JDBC Connection based transaction.
@@ -95,10 +91,11 @@ public class JdbcTransaction implements SpiTransaction {
protected boolean localReadOnly;
/**
* Set to true if using batch processing.
*/
protected boolean batchMode;
protected PersistBatch oldBatchMode;
protected PersistBatch batchMode;
protected PersistBatch batchOnCascadeMode;
protected int batchSize = -1;
@@ -109,7 +106,7 @@ public class JdbcTransaction implements SpiTransaction {
protected Boolean batchFlushOnMixed;
protected String logPrefix;
/**
* The depth used by batch processing to help the ordering of statements.
*/
@@ -120,18 +117,20 @@ public class JdbcTransaction implements SpiTransaction {
*/
protected final boolean autoCommit;
protected IdentityHashMap<Object,Object> persistingBeans;
protected IdentityHashMap<Object, Object> persistingBeans;
protected HashSet<Integer> deletingBeansHash;
protected HashMap<String,String> m2mIntersectionSave;
protected HashMap<String, String> m2mIntersectionSave;
protected HashMap<Integer, List<DerivedRelationshipData>> derivedRelMap;
protected Map<String, Object> userObjects;
protected List<TransactionCallback> callbackList;
protected boolean batchOnCascadeSet;
/**
* Create a new JdbcTransaction.
*/
@@ -143,6 +142,8 @@ public class JdbcTransaction implements SpiTransaction {
this.explicit = explicit;
this.manager = manager;
this.connection = connection;
this.batchMode = manager == null ? PersistBatch.NONE : manager.getPersistBatch();
this.batchOnCascadeMode = manager == null ? PersistBatch.NONE : manager.getPersistBatchOnCascade();
this.onQueryOnly = manager == null ? OnQueryOnly.ROLLBACK : manager.getOnQueryOnly();
this.persistenceContext = new DefaultPersistenceContext();
this.autoCommit = connection.getAutoCommit();
@@ -156,7 +157,7 @@ public class JdbcTransaction implements SpiTransaction {
}
private static String deriveLogPrefix(String id) {
StringBuilder sb = new StringBuilder();
sb.append("txn[");
if (id != null) {
@@ -165,11 +166,12 @@ public class JdbcTransaction implements SpiTransaction {
sb.append("] ");
return sb.toString();
}
@Override
public String getLogPrefix() {
return logPrefix;
}
public String toString() {
return logPrefix;
}
@@ -230,20 +232,20 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public List<DerivedRelationshipData> getDerivedRelationship(Object bean) {
if (derivedRelMap == null) {
return null;
}
Integer key = Integer.valueOf(System.identityHashCode(bean));
return derivedRelMap.get(key);
return derivedRelMap.get(System.identityHashCode(bean));
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) {
if (derivedRelMap == null) {
derivedRelMap = new HashMap<Integer, List<DerivedRelationshipData>>();
}
Integer key = Integer.valueOf(System.identityHashCode(derivedRelationship.getAssocBean()));
Integer key = new Integer(System.identityHashCode(derivedRelationship.getAssocBean()));
List<DerivedRelationshipData> list = derivedRelMap.get(key);
if (list == null) {
@@ -259,6 +261,7 @@ public class JdbcTransaction implements SpiTransaction {
* This is to handle bi-directional relationships where both sides Cascade.
* </p>
*/
@Override
public void registerDeleteBean(Integer persistingBean) {
if (deletingBeansHash == null) {
deletingBeansHash = new HashSet<Integer>();
@@ -269,6 +272,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Unregister the persisted bean.
*/
@Override
public void unregisterDeleteBean(Integer persistedBean) {
if (deletingBeansHash != null) {
deletingBeansHash.remove(persistedBean);
@@ -278,6 +282,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if this is a bean that has already been saved/deleted.
*/
@Override
public boolean isRegisteredDeleteBean(Integer persistingBean) {
return deletingBeansHash != null && deletingBeansHash.contains(persistingBean);
}
@@ -285,19 +290,21 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Unregister the persisted bean.
*/
@Override
public void unregisterBean(Object bean) {
persistingBeans.remove(bean);
}
/**
* Return true if this is a bean that has already been saved. This will
* register the bean if it is not already.
*/
@Override
public boolean isRegisteredBean(Object bean) {
if (persistingBeans == null) {
persistingBeans = new IdentityHashMap<Object,Object>();
persistingBeans = new IdentityHashMap<Object, Object>();
}
return (persistingBeans.put(bean,PLACEHOLDER) != null);
return (persistingBeans.put(bean, PLACEHOLDER) != null);
}
/**
@@ -317,8 +324,8 @@ public class JdbcTransaction implements SpiTransaction {
// first time into this intersection table so allow
m2mIntersectionSave.put(intersectionTable, beanName);
return true;
}
}
// only allow if save coming from the same bean type
// to stop saves coming from both directions of m2m
return existingBean.equals(beanName);
@@ -336,16 +343,25 @@ public class JdbcTransaction implements SpiTransaction {
* <p>
* The depth is used to help the ordering of batched statements.
* </p>
*
* @param diff
* the amount to add or subtract from the depth.
*
* @param diff the amount to add or subtract from the depth.
* @return the current depth plus the diff
*/
@Override
public int depth(int diff) {
depth += diff;
return depth;
}
/**
* Return the current depth.
*/
@Override
public int depth() {
return depth;
}
@Override
public boolean isReadOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -357,6 +373,7 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public void setReadOnly(boolean readOnly) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -369,13 +386,41 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public void setBatchMode(boolean batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchMode = (batchMode) ? PersistBatch.ALL : PersistBatch.NONE;
}
@Override
public void setBatch(PersistBatch batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchMode = batchMode;
}
@Override
public PersistBatch getBatch() {
return batchMode;
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchOnCascadeMode = batchOnCascadeMode;
}
@Override
public PersistBatch getBatchOnCascade() {
return batchOnCascadeMode;
}
@Override
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
this.batchGetGeneratedKeys = getGeneratedKeys;
if (batchControl != null) {
@@ -383,6 +428,7 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
this.batchFlushOnMixed = batchFlushOnMixed;
if (batchControl != null) {
@@ -396,10 +442,12 @@ public class JdbcTransaction implements SpiTransaction {
* Returning 0 implies to use the system wide default batch size.
* </p>
*/
@Override
public int getBatchSize() {
return batchSize;
}
@Override
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
if (batchControl != null) {
@@ -407,10 +455,12 @@ public class JdbcTransaction implements SpiTransaction {
}
}
@Override
public boolean isBatchFlushOnQuery() {
return batchFlushOnQuery;
}
@Override
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
this.batchFlushOnQuery = batchFlushOnQuery;
}
@@ -419,15 +469,113 @@ public class JdbcTransaction implements SpiTransaction {
* Return true if this request should be batched. Returning false means that
* this request should be executed immediately.
*/
public boolean isBatchThisRequest() {
if (!explicit && depth <= 0) {
// implicit transaction ... no gain
// by batching where depth <= 0
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
if (!batchOnCascadeSet && !explicit && depth <= 0) {
// implicit transaction, no gain by batching where depth <= 0
return false;
}
return batchMode;
switch (batchMode) {
case ALL:
return true;
case INSERT:
return type == PersistRequest.Type.INSERT;
default:
return false;
}
}
/**
* Return true if JDBC batch should be used on cascade persist.
*/
private boolean isBatchOnCascade(PersistRequest.Type type) {
switch (batchOnCascadeMode) {
case ALL:
return true;
case INSERT:
return type == PersistRequest.Type.INSERT;
default:
return false;
}
}
public void checkBatchEscalationOnCollection() {
if (batchMode == PersistBatch.NONE && batchOnCascadeMode != PersistBatch.NONE) {
batchMode = batchOnCascadeMode;
batchOnCascadeSet = true;
}
}
public void flushBatchOnCollection() {
if (batchOnCascadeSet) {
if (batchControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("... flushBatchOnCollection");
}
batchControl.flushReset();
}
// restore the previous batch mode of NONE
batchMode = PersistBatch.NONE;
}
}
/**
* Flush after completing persist cascade.
*/
@Override
public void flushBatchOnCascade() {
if (batchControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("... flushBatchOnCascade");
}
batchControl.flushReset();
}
// restore the previous batch mode
batchMode = oldBatchMode;
}
private boolean isAlreadyBatching(PersistRequest.Type type) {
switch (batchMode) {
case ALL:
return true;
case INSERT:
return type == PersistRequest.Type.INSERT;
default:
return false;
}
}
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
if (isAlreadyBatching(request.getType())) {
// already batching (at top level)
return false;
}
if (isBatchOnCascade(request.getType())) {
// escalate up to batch mode for this request (and cascade)
oldBatchMode = batchMode;
batchMode = PersistBatch.ALL;
if (batchControl != null) {
// flush with reset so that this request goes into it's own batch buffer
batchControl.flushReset();
}
// skip using jdbc batch for the top level bean (no gain there)
request.setSkipBatchForTopLevel();
return true;
}
if (batchControl != null && !batchControl.isEmpty()) {
if (logger.isTraceEnabled()) {
logger.trace("... flush from batchOnCascade ");
}
batchControl.flushReset();
}
return false;
}
@Override
public BatchControl getBatchControl() {
return batchControl;
}
@@ -436,6 +584,7 @@ public class JdbcTransaction implements SpiTransaction {
* Set the BatchControl to the transaction. This is done once per transaction
* on the first persist request.
*/
@Override
public void setBatchControl(BatchControl batchControl) {
queryOnly = false;
this.batchControl = batchControl;
@@ -458,6 +607,7 @@ public class JdbcTransaction implements SpiTransaction {
* executing.
* </p>
*/
@Override
public void flushBatch() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -467,13 +617,10 @@ public class JdbcTransaction implements SpiTransaction {
}
}
public void batchFlush() {
flushBatch();
}
/**
* Return the persistence context associated with this transaction.
*/
@Override
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -486,6 +633,7 @@ public class JdbcTransaction implements SpiTransaction {
* then set it back later to a second transaction.
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -496,6 +644,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying TransactionEvent.
*/
@Override
public TransactionEvent getEvent() {
queryOnly = false;
if (event == null) {
@@ -507,29 +656,35 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if this was an explicitly created transaction.
*/
@Override
public boolean isExplicit() {
return explicit;
}
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.trace(Str.add(logPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
}
/**
* Return the transaction id.
*/
@Override
public String getId() {
return id;
}
@@ -537,6 +692,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying connection for internal use.
*/
@Override
public Connection getInternalConnection() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -547,6 +703,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying connection for public use.
*/
@Override
public Connection getConnection() {
queryOnly = false;
return getInternalConnection();
@@ -598,7 +755,7 @@ public class JdbcTransaction implements SpiTransaction {
manager.notifyOfQueryOnly(true, this, null);
}
}
/**
* Rollback, Commit or Close for query only transaction.
* <p>
@@ -609,17 +766,17 @@ public class JdbcTransaction implements SpiTransaction {
protected void connectionEndForQueryOnly() {
try {
switch (onQueryOnly) {
case ROLLBACK:
performRollback();
break;
case COMMIT:
performCommit();
break;
case CLOSE_ON_READCOMMITTED:
// valid at READ COMMITTED Isolation
break;
default:
performRollback();
case ROLLBACK:
performRollback();
break;
case COMMIT:
performCommit();
break;
case CLOSE_ON_READCOMMITTED:
// valid at READ COMMITTED Isolation
break;
default:
performRollback();
}
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
@@ -643,6 +800,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* End the transaction on a query only request.
*/
@Override
public void endQueryOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -652,13 +810,14 @@ public class JdbcTransaction implements SpiTransaction {
} finally {
// these will not throw an exception
deactivate();
notifyQueryOnly();
notifyQueryOnly();
}
}
/**
* Commit the transaction.
*/
@Override
public void commit() throws RollbackException {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -680,12 +839,12 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception e) {
throw new RollbackException(e);
} finally {
// these will not throw an exception
firePostCommit();
deactivate();
notifyCommit();
notifyCommit();
}
}
@@ -705,6 +864,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Rollback the transaction.
*/
@Override
public void rollback() throws PersistenceException {
rollback(null);
}
@@ -713,6 +873,7 @@ public class JdbcTransaction implements SpiTransaction {
* Rollback the transaction. If there is a throwable it is logged as the cause
* in the transaction log.
*/
@Override
public void rollback(Throwable cause) throws PersistenceException {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -723,7 +884,7 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception ex) {
throw new PersistenceException(ex);
} finally {
// these will not throw an exception
firePostRollback();
@@ -735,6 +896,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* If the transaction is active then perform rollback.
*/
@Override
public void end() throws PersistenceException {
if (isActive()) {
rollback();
@@ -744,29 +906,35 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if the transaction is active.
*/
@Override
public boolean isActive() {
return active;
}
@Override
public boolean isPersistCascade() {
return persistCascade;
}
@Override
public void setPersistCascade(boolean persistCascade) {
this.persistCascade = persistCascade;
}
@Override
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
getEvent().add(tableName, inserts, updates, deletes);
}
@Override
public void putUserObject(String name, Object value) {
if (userObjects == null) {
userObjects = new HashMap<String,Object>();
userObjects = new HashMap<String, Object>();
}
userObjects.put(name, value);
}
@Override
public Object getUserObject(String name) {
if (userObjects == null) {
return null;
@@ -777,11 +945,12 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Alias for end(), which enables this class to be used in try-with-resources.
*/
@Override
public void close() throws IOException {
try {
end();
end();
} catch (PersistenceException ex) {
throw new IOException(ex);
throw new IOException(ex);
}
}
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -37,8 +38,8 @@ public class TransactionManager {
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SUM");
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN");
/**
/**
* The behavior desired when ending a query only transaction.
*/
public enum OnQueryOnly {
@@ -79,32 +80,33 @@ public class TransactionManager {
*/
protected final OnQueryOnly onQueryOnly;
/**
* The default batchMode for transactions.
*/
protected final boolean defaultBatchMode;
protected final BackgroundExecutor backgroundExecutor;
protected final ClusterManager clusterManager;
protected final String serverName;
protected final PersistBatch persistBatch;
protected final PersistBatch persistBatchOnCascade;
/**
* Id's for transaction logging.
*/
protected AtomicLong transactionCounter = new AtomicLong(1000);
protected final AtomicLong transactionCounter = new AtomicLong(1000);
protected final BulkEventListenerMap bulkEventListenerMap;
protected TransactionEventListener[] transactionEventListeners;
protected final TransactionEventListener[] transactionEventListeners;
/**
* Create the TransactionManager
*/
public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config,
BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
this.persistBatch = config.getPersistBatch();
this.persistBatchOnCascade = config.getPersistBatchOnCascade();
this.beanDescriptorManager = descMgr;
this.clusterManager = clusterManager;
this.serverName = config.getName();
@@ -115,7 +117,6 @@ public class TransactionManager {
List<TransactionEventListener> transactionEventListeners = bootupClasses.getTransactionEventListeners();
this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
this.defaultBatchMode = config.isPersistBatching();
this.prefix = "";
this.externalTransPrefix = "e";
@@ -145,8 +146,16 @@ public class TransactionManager {
public BulkEventListenerMap getBulkEventListenerMap() {
return bulkEventListenerMap;
}
/**
public PersistBatch getPersistBatch() {
return persistBatch;
}
public PersistBatch getPersistBatchOnCascade() {
return persistBatchOnCascade;
}
/**
* Return the behaviour to use when a query only transaction is committed.
* <p>
* There is a potential optimisation available when read committed is the default
@@ -232,12 +241,9 @@ public class TransactionManager {
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
// set the default batch mode. This can be on for
// jdbc drivers that support getGeneratedKeys
if (defaultBatchMode){
t.setBatchMode(true);
}
// set the default batch mode
t.setBatch(persistBatch);
t.setBatchOnCascade(persistBatchOnCascade);
return t;
}
@@ -251,12 +257,6 @@ public class TransactionManager {
long id = transactionCounter.incrementAndGet();
SpiTransaction t = createTransaction(explicit, c, id);
// set the default batch mode. This can be on for
// jdbc drivers that support getGeneratedKeys
if (defaultBatchMode){
t.setBatchMode(true);
}
if (isolationLevel > -1) {
c.setTransactionIsolation(isolationLevel);
}
@@ -286,16 +286,8 @@ public class TransactionManager {
c = dataSource.getConnection();
long id = transactionCounter.incrementAndGet();
SpiTransaction t = createTransaction(false, c, id);
// set the default batch mode. Can be true for
// jdbc drivers that support getGeneratedKeys
if (defaultBatchMode){
t.setBatchMode(true);
}
return t;
return createTransaction(false, c, id);
} catch (PersistenceException ex) {
// close the connection and re-throw the exception
try {
@@ -454,8 +446,6 @@ public class TransactionManager {
beanPersist.notifyCacheAndListener();
}
}
}
}
@@ -0,0 +1,50 @@
package com.avaje.ebean.config;
import org.junit.Test;
import java.util.Properties;
import static org.junit.Assert.*;
public class ServerConfigTest {
@Test
public void testLoadFromEbeanProperties() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.loadFromProperties();
assertEquals(PersistBatch.NONE, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
}
@Test
public void testLoadWithProperties() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setPersistBatch(PersistBatch.NONE);
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
Properties props = new Properties();
props.setProperty("persistBatch", "INSERT");
props.setProperty("persistBatchOnCascade", "INSERT");
serverConfig.loadFromProperties(props);
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade());
serverConfig.setPersistBatch(PersistBatch.NONE);
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
Properties props1 = new Properties();
props1.setProperty("ebean.persistBatch", "ALL");
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
serverConfig.loadFromProperties(props1);
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
}
}
@@ -0,0 +1,118 @@
package com.avaje.ebean.elasticsearch;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.tests.model.basic.Customer;
import com.avaje.tests.model.basic.ResetBasicData;
import com.fasterxml.jackson.core.JsonGenerator;
import com.squareup.okhttp.*;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
public class TestBasicPush extends BaseTestCase {
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
@Ignore
@Test
public void testBulkUpdate() throws IOException {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
JsonContext jsonContext = server.json();
StringWriter writer = new StringWriter();
JsonGenerator generator = jsonContext.createGenerator(writer);
List<Customer> customers = Ebean.find(Customer.class).findList();
for (Customer customer : customers) {
customer.setName(customer.getName()+"esMod");
PathProperties updateProps = PathProperties.parse("name");
generator.writeStartObject();
generator.writeFieldName("update");
generator.writeStartObject();
generator.writeStringField("_id", customer.getId().toString());
generator.writeStringField("_type", "customer");
generator.writeStringField("_index", "customer");
generator.writeEndObject();
generator.writeEndObject();
generator.writeRaw("\n");
generator.writeStartObject();
generator.writeFieldName("doc");
jsonContext.toJson(customer, generator, updateProps);
generator.writeEndObject();
generator.writeRaw("\n");
}
generator.close();
String json = writer.toString();
System.out.println(json);
String response = post("http://localhost:9200/_bulk", json);
System.out.println(response);
//curl -s -XPOST localhost:9200/_bulk
// { "update" : {"_id" : "1", "_type" : "type1", "_index" : "index1"} }
// { "doc" : {"field2" : "value2"} }
}
@Ignore
@Test
public void test() throws IOException {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
JsonContext jsonContext = server.json();
List<Customer> customers = Ebean.find(Customer.class).findList();
PathProperties paths = PathProperties.parse("name, status, anniversary");
for (Customer customer : customers) {
String json = jsonContext.toJson(customer, paths);
put("http://localhost:9200/customer/customer/"+customer.getId(), json);
}
}
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.put(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
String put(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.put(body)
.build();
Response response = client.newCall(request).execute();
return response.body().string();
}
}
@@ -0,0 +1,132 @@
package com.avaje.ebeaninternal.server.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.config.PersistBatch;
import com.avaje.tests.model.basic.UTDetail;
import com.avaje.tests.model.basic.UTMaster;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import static org.junit.Assert.assertTrue;
public class TestBatchPersistCascade extends BaseTestCase {
Logger logger = LoggerFactory.getLogger(TestBatchPersistCascade.class);
@Test
public void test() {
EbeanServer ebeanServer = Ebean.getServer(null);
LoggedSqlCollector.start();
Transaction txn = ebeanServer.beginTransaction();
try {
txn.setBatch(PersistBatch.INSERT);
logger.info("start ------------");
for (int i = 0; i < 3; i++) {
UTMaster master = createMaster(i);
logger.info("save ------------ {}", i);
ebeanServer.save(master);
//txn.flushBatch();
}
logger.info("commit ------------");
txn.commit();
} finally {
txn.end();
}
List<String> loggedSql = LoggedSqlCollector.stop();
assertTrue(loggedSql.size() > 2);
testUpdates();
}
private void testUpdates() {
EbeanServer server = Ebean.getServer(null);
List<UTMaster> list = server.find(UTMaster.class).fetch("details").findList();
Transaction txn = server.beginTransaction();
try {
txn.setBatch(PersistBatch.INSERT);
txn.setBatchOnCascade(PersistBatch.ALL);
for (int i = 0; i < 3; i++) {
UTMaster master = createMaster(i+500);
logger.info("save ------------ {}", i);
server.save(master);
}
logger.info("starting updates ------------ ");
UTMaster lastMaster = null;
for (UTMaster utMaster : list) {
utMaster.setName(utMaster.getName()+" + mod");
List<UTDetail> details = utMaster.getDetails();
for (UTDetail detail : details) {
detail.setQty(detail.getQty()+7);
detail.setName(detail.getName()+" + foo");
}
server.save(utMaster);
lastMaster = utMaster;
}
logger.info("starting some inserts ------------ ");
for (int i = 0; i < 3; i++) {
UTMaster master = createMaster(i+1000);
logger.info("save ------------ {}", i);
server.save(master);
if (i == 1) {
logger.info("save lastMaster ------------ ");
lastMaster.setName("mod");
server.save(lastMaster);
}
}
logger.info("commit ------------ ");
server.commitTransaction();
} finally {
server.endTransaction();
}
}
private UTDetail createUTDetail(String master, int count) {
UTDetail detail = new UTDetail();
detail.setName(master+"-"+count);
detail.setAmount(50d);
detail.setQty(count);
return detail;
}
private UTMaster createMaster(int count) {
String name = "master"+count;
UTMaster m0 = new UTMaster();
m0.setName(name);
for (int i =0; i< 5; i++) {
m0.addDetail(createUTDetail(name, i));
}
return m0;
}
}
@@ -23,7 +23,7 @@ public class ScalarTypeLocalDateTimeTest {
long now = System.currentTimeMillis();
long toMillis = type.convertToMillis(LocalDateTime.now());
assertTrue(toMillis - now < 10);
assertTrue(toMillis - now < 30);
}
@Test
@@ -1,53 +1,134 @@
package com.avaje.tests.batchinsert;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.annotation.Transactional;
import com.avaje.ebean.config.PersistBatch;
import com.avaje.tests.model.basic.UTDetail;
import com.avaje.tests.model.basic.UTMaster;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TestBatchInsertSimple extends BaseTestCase {
Random random = new Random();
@Test
public void testSimpleJdbcBatching() {
public void testJdbcBatchPerRequestWithMasterAndDetails() {
int numOfMasters = 10;// 2 + random.nextInt(8);
int numOfMasters = 4;// 2 + random.nextInt(8);
Transaction transaction = Ebean.beginTransaction();
try {
transaction.setBatch(PersistBatch.NONE);
transaction.setBatchOnCascade(PersistBatch.INSERT);
transaction.setBatchSize(30);
for (int i = 0; i < numOfMasters; i++) {
UTMaster master = createMasterAndDetails(i, 20);
master.save();
}
transaction.commit();
} finally {
transaction.end();
}
}
@Test
public void testTransactional() {
saveWithFullBatchMode();
}
@Transactional(batch=PersistBatch.ALL, batchSize=50)
public void saveWithFullBatchMode() {
int numOfMasters = 4;
for (int i = 0; i < numOfMasters; i++) {
UTMaster master = createMasterAndDetails(i, 5);
// the save is 'batched' and does not execute immediately
// ... it now acts more like 'merge/persist'
master.save();
}
}
@Test
public void testJdbcBatchPerRequestWithMasterOnly() {
int numOfMasters = 4;
Transaction transaction = Ebean.beginTransaction();
try {
transaction.setBatch(PersistBatch.NONE);
transaction.setBatchOnCascade(PersistBatch.INSERT);
transaction.setBatchSize(30);
for (int i = 0; i < numOfMasters; i++) {
UTMaster master = createMaster(i);
Ebean.save(master);
}
transaction.commit();
} finally {
transaction.end();
}
}
@Test
public void testJdbcBatchOnCollection() {
int numOfMasters = 3;
List<UTMaster> masters = new ArrayList<UTMaster>();
for (int i = 0; i < numOfMasters; i++) {
masters.add(createMasterAndDetails(i));
masters.add(createMasterAndDetails(i, 7));
}
Transaction transaction = Ebean.beginTransaction();
try {
transaction.setBatchMode(true);
transaction.setBatchSize(4);
// transaction.setLogLevel(LogLevel.SUMMARY);
// transaction.setBatchGetGeneratedKeys(false);
transaction.setBatch(PersistBatch.NONE);
transaction.setBatchOnCascade(PersistBatch.ALL);
transaction.setBatchSize(20);
// escalate based on batchOnCascade value
Ebean.save(masters);
transaction.commit();
} finally {
Ebean.endTransaction();
transaction.end();
}
}
private UTMaster createMasterAndDetails(int masterPos) {
@Test
public void testJdbcBatchOnCollectionNoTransaction() {
int numOfMasters = 3;
List<UTMaster> masters = new ArrayList<UTMaster>();
for (int i = 0; i < numOfMasters; i++) {
masters.add(createMasterAndDetails(i, 5));
}
// escalate based on batchOnCascade value
Ebean.save(masters);
}
private UTMaster createMasterAndDetails(int masterPos, int size) {
UTMaster master = createMaster(masterPos);
List<UTDetail> details = new ArrayList<UTDetail>();
int count = 2 + random.nextInt(20);
int count = 2 + random.nextInt(size);
for (int i = 0; i < count; i++) {
@@ -70,8 +151,8 @@ public class TestBatchInsertSimple extends BaseTestCase {
UTDetail detail = new UTDetail();
detail.setName("batchInsert-detail-" + position);
detail.setQty(Integer.valueOf(qty));
detail.setAmount(Double.valueOf(amount));
detail.setQty(qty);
detail.setAmount(amount);
// System.out.println("-- "+detail);
@@ -1,5 +1,7 @@
package com.avaje.tests.model.basic;
import com.avaje.ebean.Model;
import java.util.ArrayList;
import java.util.List;
@@ -12,7 +14,7 @@ import javax.persistence.Version;
@Entity
@Table(name="ut_master")
public class UTMaster {
public class UTMaster extends Model {
@Id
Integer id;
@@ -4,6 +4,7 @@ import java.util.List;
import javax.persistence.EntityNotFoundException;
import com.avaje.ebean.config.PersistBatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,6 +13,8 @@ import com.avaje.ebean.Transaction;
import com.avaje.ebean.TxType;
import com.avaje.ebean.annotation.Transactional;
import static org.junit.Assert.assertEquals;
public class DummyDao {
Logger logger = LoggerFactory.getLogger(DummyDao.class);
@@ -33,5 +36,34 @@ public class DummyDao {
public void addToObject(Long id, Double anotherNumber, List<Long> ids) throws EntityNotFoundException {
// and more code
}
@Transactional(batch = PersistBatch.ALL, batchOnCascade = PersistBatch.ALL, batchSize = 99)
public void doWithBatchOptionsSet() {
Transaction txn = Ebean.currentTransaction();
assertEquals(PersistBatch.ALL, txn.getBatch());
assertEquals(PersistBatch.ALL, txn.getBatchOnCascade());
assertEquals(99, txn.getBatchSize());
}
@Transactional(batch = PersistBatch.INSERT, batchOnCascade = PersistBatch.NONE, batchSize = 77)
public void doOuterWithBatchOptionsSet() {
Transaction txn = Ebean.currentTransaction();
assertEquals(PersistBatch.INSERT, txn.getBatch());
assertEquals(PersistBatch.NONE, txn.getBatchOnCascade());
assertEquals(77, txn.getBatchSize());
doWithBatchOptionsSet();
// batch options set back
assertEquals(PersistBatch.INSERT, txn.getBatch());
assertEquals(PersistBatch.NONE, txn.getBatchOnCascade());
assertEquals(77, txn.getBatchSize());
}
}
@@ -0,0 +1,66 @@
package com.avaje.tests.model.basic.xtra;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.PersistBatch;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TestInsertBatchThenFlushThenUpdate extends BaseTestCase {
@Test
public void test() {
LoggedSqlCollector.start();
Transaction txn = Ebean.beginTransaction();
try {
txn.setBatch(PersistBatch.ALL);
EdParent parent = new EdParent();
parent.setName("MyComputer");
EdChild child = new EdChild();
child.setName("Harddisk 123");
child.setParent(parent);
ArrayList<EdChild> children = new ArrayList<EdChild>();
children.add(child);
parent.setChildren(children);
Ebean.save(parent);
// nothing flushed yet
assertEquals(0, LoggedSqlCollector.start().size());
txn.flushBatch();
List<String> loggedSql1 = LoggedSqlCollector.start();
assertEquals(loggedSql1.toString(), 2, loggedSql1.size());
parent.setName("MyDesk");
Ebean.save(parent);
// nothing flushed yet
assertEquals(0, LoggedSqlCollector.start().size());
Ebean.commitTransaction();
// insert statements for EdExtendedParent
List<String> loggedSql2 = LoggedSqlCollector.start();
assertEquals(2, loggedSql2.size());
assertTrue(loggedSql2.get(0).contains(" update td_parent "));
assertTrue(loggedSql2.get(1).contains(" update td_child "));
} finally {
Ebean.endTransaction();
}
}
}
@@ -0,0 +1,59 @@
package com.avaje.tests.model.basic.xtra;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.PersistBatch;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class TestInsertBatchThenUpdate extends BaseTestCase {
@Test
public void test() {
LoggedSqlCollector.start();
Transaction txn = Ebean.beginTransaction();
try {
txn.setBatch(PersistBatch.ALL);
EdParent parent = new EdParent();
parent.setName("MyComputer");
EdChild child = new EdChild();
child.setName("Harddisk 123");
child.setParent(parent);
ArrayList<EdChild> children = new ArrayList<EdChild>();
children.add(child);
parent.setChildren(children);
Ebean.save(parent);
// nothing flushed yet
List<String> loggedSql0 = LoggedSqlCollector.start();
assertEquals(0, loggedSql0.size());
parent.setName("MyDesk");
Ebean.save(parent);
// nothing flushed yet
assertEquals(0, LoggedSqlCollector.start().size());
Ebean.commitTransaction();
// insert statements for EdExtendedParent
List<String> loggedSql2 = LoggedSqlCollector.start();
assertEquals(2, loggedSql2.size());
} finally {
Ebean.endTransaction();
}
}
}
@@ -0,0 +1,70 @@
package com.avaje.tests.model.basic.xtra;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.PersistBatch;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class TestInsertBatchWithDifferentRootTypes extends BaseTestCase {
@Test
public void testDifferRootTypes() {
LoggedSqlCollector.start();
Transaction txn = Ebean.beginTransaction();
try {
txn.setBatch(PersistBatch.ALL);
EdParent parent = new EdParent();
parent.setName("MyComputer");
EdChild child = new EdChild();
child.setName("Harddisk 123");
child.setParent(parent);
ArrayList<EdChild> children = new ArrayList<EdChild>();
children.add(child);
parent.setChildren(children);
Ebean.save(parent);
EdExtendedParent extendedParent = new EdExtendedParent();
extendedParent.setName("My second computer");
extendedParent.setExtendedName("Multimedia");
child = new EdChild();
child.setName("DVBS Card");
children = new ArrayList<EdChild>();
children.add(child);
extendedParent.setChildren(children);
// nothing flushed yet
List<String> loggedSql0 = LoggedSqlCollector.start();
assertEquals(0, loggedSql0.size());
// causes a flush as EdExtendedParent is different from EdParent
Ebean.save(extendedParent);
// insert statements for EdParent
List<String> loggedSql1 = LoggedSqlCollector.start();
assertEquals(2, loggedSql1.size());
Ebean.commitTransaction();
// insert statements for EdExtendedParent
List<String> loggedSql2 = LoggedSqlCollector.start();
assertEquals(2, loggedSql2.size());
} finally {
Ebean.endTransaction();
}
}
}
@@ -0,0 +1,132 @@
package com.avaje.tests.query;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.PagedList;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
public class TestQueryFindPagedList extends BaseTestCase {
@Test
public void test_noCount() throws ExecutionException, InterruptedException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
List<Order> orders = pagedList.getList();
assertTrue(!orders.isEmpty());
List<String> loggedSql = LoggedSqlCollector.stop();
assertEquals("Only 1 SQL statement, no count query",1, loggedSql.size());
}
@Test
public void test_countInBackground() throws ExecutionException, InterruptedException, TimeoutException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
Future<Integer> rowCount = pagedList.getFutureRowCount();
List<Order> orders = pagedList.getList();
// these are each getting the total row count
int totalRowCount = pagedList.getTotalRowCount();
Integer totalRowCountWithTimeout = rowCount.get(30, TimeUnit.SECONDS);
Integer totalRowCountViaFuture = rowCount.get();
List<String> loggedSql = LoggedSqlCollector.stop();
assertTrue(orders.size() < totalRowCount);
assertEquals(Integer.valueOf(totalRowCount), totalRowCountViaFuture);
assertEquals(Integer.valueOf(totalRowCount), totalRowCountWithTimeout);
assertEquals(2, loggedSql.size());
String firstTxn = loggedSql.get(0).substring(0, 10);
String secTxn = loggedSql.get(1).substring(0, 10);
assertNotEquals(firstTxn, secTxn);
}
@Test
public void test_countInBackground_withLoadRowCount() {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
pagedList.loadRowCount();
List<Order> orders = pagedList.getList();
int totalRowCount = pagedList.getTotalRowCount();
List<String> loggedSql = LoggedSqlCollector.stop();
assertTrue(orders.size() < totalRowCount);
assertEquals(2, loggedSql.size());
String firstTxn = loggedSql.get(0).substring(0, 10);
String secTxn = loggedSql.get(1).substring(0, 10);
assertNotEquals(firstTxn, secTxn);
}
@Test
public void test_countUsingForegound() throws ExecutionException, InterruptedException {
ResetBasicData.reset();
PagedList<Order> pagedList = Ebean.find(Order.class).findPagedList(0, 3);
LoggedSqlCollector.start();
// kinda not normal but just wrap in a transaction to assert
// the background fetch does not occur (which explicitly creates
// its own transaction) ... so a bit naughty with the test here
Ebean.beginTransaction();
try {
List<Order> orders = pagedList.getList();
int totalRowCount = pagedList.getTotalRowCount();
// invoke it again but cached...
int totalRowCountAgain = pagedList.getTotalRowCount();
List<String> loggedSql = LoggedSqlCollector.stop();
assertTrue(orders.size() < totalRowCount);
assertEquals(2, loggedSql.size());
assertEquals(totalRowCount, totalRowCountAgain);
String firstTxn = loggedSql.get(0).substring(0, 10);
String secTxn = loggedSql.get(1).substring(0, 10);
assertEquals(firstTxn, secTxn);
} finally {
Ebean.endTransaction();
}
}
}
@@ -2,18 +2,39 @@ package com.avaje.tests.query.joins;
import java.util.List;
import com.avaje.ebean.*;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.FetchConfig;
import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TestQueryJoinManyNonRoot extends BaseTestCase {
@Test
public void test_manyPredicate() {
ResetBasicData.reset();
LoggedSqlCollector.start();
List<Order> orders = Ebean.find(Order.class)
.select("id, status, orderDate")
.where().gt("details.orderQty", 0)
.findList();
assertTrue(!orders.isEmpty());
List<String> loggedSql = LoggedSqlCollector.stop();
assertEquals(1, loggedSql.size());
assertTrue(loggedSql.get(0).contains("select distinct "));
assertTrue(loggedSql.get(0).contains(" from o_order t0 join o_order_detail u1 on u1.order_id = t0.id "));
}
@Test
public void test_manyNonRoot() {
@@ -27,9 +48,9 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
List<Order> list = q.findList();
String sql = q.getGeneratedSql();
Assert.assertTrue(list.size() > 0);
Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
Assert.assertTrue(sql.contains("left outer join contact t2 on"));
assertTrue(list.size() > 0);
assertTrue(sql.contains("join o_customer t1 on t1.id "));
assertTrue(sql.contains("left outer join contact t2 on"));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6,
// t1.id c7, t1.status c8, t1.name c9, t1.smallnote c10, t1.anniversary c11, t1.cretime c12, t1.updtime c13, t1.billing_address_id c14, t1.shipping_address_id c15,
@@ -56,10 +77,10 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
List<Order> list = q.findList();
String sql = q.getGeneratedSql();
Assert.assertTrue(list.size() > 0);
Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
Assert.assertTrue(sql.contains("left outer join o_order_detail "));
Assert.assertTrue(sql.contains("left outer join o_product "));
assertTrue(list.size() > 0);
assertTrue(sql.contains("join o_customer t1 on t1.id "));
assertTrue(sql.contains("left outer join o_order_detail "));
assertTrue(sql.contains("left outer join o_product "));
Assert.assertFalse(sql.contains("left outer join contact"));
@@ -84,9 +105,9 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
order.getCustomer().getContacts().size();
}
Assert.assertTrue(list.size() > 0);
Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
Assert.assertTrue(sql.contains("left outer join contact "));
assertTrue(list.size() > 0);
assertTrue(sql.contains("join o_customer t1 on t1.id "));
assertTrue(sql.contains("left outer join contact "));
Assert.assertFalse(sql.contains("left outer join o_order_detail "));
Assert.assertFalse(sql.contains("left outer join o_product "));
@@ -18,6 +18,15 @@ public class TestTxTypeOnTransactional extends BaseTestCase {
Logger logger = LoggerFactory.getLogger(TestTxTypeOnTransactional.class);
@Test
public void testBatchOptionsAreSet() {
logger.info("-- test pre doOuterWithBatchOptionsSet");
DummyDao dao = new DummyDao();
dao.doOuterWithBatchOptionsSet();
logger.info("-- test post doOuterWithBatchOptionsSet");
}
@Test
public void test() {
+2
View File
@@ -27,6 +27,8 @@ ebean.autofetch.traceUsageCollection=false
ebean.ddl.generate=true
ebean.ddl.run=true
ebean.persistBatch=NONE
ebean.persistBatchOnCascade=ALL
ebean.debug.sql=true
#ebean.debug.lazyload=false