mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74c9c14838 | ||
|
|
fc53e281e8 | ||
|
|
0c5e092d16 | ||
|
|
1260edc8ed | ||
|
|
de372bfb89 | ||
|
|
2ed5e026e0 | ||
|
|
6c37dc494b | ||
|
|
6807499d01 | ||
|
|
cb6faa022b | ||
|
|
3b30388b35 | ||
|
|
db57404e18 | ||
|
|
bede89fe13 | ||
|
|
c9fb28b288 | ||
|
|
a192b0cdbf | ||
|
|
0a15cdd7b2 | ||
|
|
14c5a1328b | ||
|
|
cac6333c5e | ||
|
|
f7d1291a41 | ||
|
|
151e44ff67 | ||
|
|
4f24944ec4 | ||
|
|
c3ccb1afb5 | ||
|
|
ca8f529629 | ||
|
|
2e03452e2e | ||
|
|
f78f4ea64a | ||
|
|
81aeca5475 | ||
|
|
36b5b3b817 | ||
|
|
d83a70a155 | ||
|
|
c140574eba | ||
|
|
4ab2f0df3c | ||
|
|
a447361ea0 | ||
|
|
8fc3f2da15 | ||
|
|
a838bc38c5 | ||
|
|
84e853bcfa | ||
|
|
e2a508aa5e | ||
|
|
e9c66abfe6 | ||
|
|
050aad717d | ||
|
|
30e02de6f3 | ||
|
|
b8a7017ffd | ||
|
|
da61136465 | ||
|
|
90ec7bafb3 | ||
|
|
c638ad8d56 | ||
|
|
6a582c0eef | ||
|
|
0551b3066f | ||
|
|
af36c9ec7d | ||
|
|
bd99badb28 | ||
|
|
f7e07633a1 | ||
|
|
ace45a038e | ||
|
|
36dd2bbee7 | ||
|
|
71e7baff01 | ||
|
|
ef093b2c6c | ||
|
|
14de498955 | ||
|
|
bcda8576bc | ||
|
|
0d35c8cb1b | ||
|
|
6362634924 | ||
|
|
676a9c8d6c | ||
|
|
b1b9f68b7e | ||
|
|
961b219e3e | ||
|
|
a998c0be55 | ||
|
|
e0159ccb4f | ||
|
|
423066ada7 | ||
|
|
c9d968b4b0 | ||
|
|
34be7cc1f1 | ||
|
|
54f21836b0 | ||
|
|
4adf5a9eba | ||
|
|
b6a0653a3c | ||
|
|
c0810a375e | ||
|
|
90fe19eb8d | ||
|
|
43ecfe7c19 | ||
|
|
15be5dd4c7 | ||
|
|
d54cffacea | ||
|
|
965de6e84d | ||
|
|
56fd6b90be | ||
|
|
dd85eba3f0 | ||
|
|
d1a40d5676 | ||
|
|
b0462bcda6 | ||
|
|
59428bb214 | ||
|
|
73cf3e8511 | ||
|
|
beff4465b0 | ||
|
|
563cdd3b3b | ||
|
|
1bd7b99235 | ||
|
|
bb9d9f7e17 | ||
|
|
6bb38f2674 | ||
|
|
bb6571f426 | ||
|
|
1ee679c5ae | ||
|
|
e5264ca69e | ||
|
|
3a86be5f3d | ||
|
|
9f71b0df22 |
@@ -1,12 +1,16 @@
|
||||
[](https://waffle.io/ebean-orm/avaje-ebeanorm)
|
||||
avaje-ebeanorm
|
||||
Ebean ORM
|
||||
==============
|
||||
|
||||
Project Website
|
||||
----------------
|
||||
[http://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
|
||||
|
||||
|
||||
Maven Dependency
|
||||
----------------
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.3.1</version>
|
||||
<version>4.5.5</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.4.1</version>
|
||||
<version>4.5.6</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>
|
||||
@@ -221,6 +228,13 @@
|
||||
<include>**/*Test.java</include>
|
||||
<include>**/*Tests.java</include>
|
||||
</includes>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<!-- transfer datasource.default parameter -->
|
||||
<name>datasource.default</name>
|
||||
<value>${datasource.default}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -241,8 +255,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();
|
||||
}
|
||||
@@ -16,8 +16,6 @@ class DRawSqlParser {
|
||||
|
||||
public static final String $_WHERE = "${where}";
|
||||
|
||||
private static final String ORDER_BY = "order by";
|
||||
|
||||
private final SimpleTextParser textParser;
|
||||
|
||||
private String sql;
|
||||
@@ -35,6 +33,7 @@ class DRawSqlParser {
|
||||
private int groupByPos = -1;
|
||||
private int havingPos = -1;
|
||||
private int orderByPos = -1;
|
||||
private int orderByStmtPos = -1;
|
||||
|
||||
private boolean whereExprAnd;
|
||||
private int whereExprPos = -1;
|
||||
@@ -66,12 +65,12 @@ class DRawSqlParser {
|
||||
String preFrom = removeWhitespace(findPreFromSql());
|
||||
String preWhere = removeWhitespace(findPreWhereSql());
|
||||
String preHaving = removeWhitespace(findPreHavingSql());
|
||||
String orderByPrefix = findOrderByPrefixSql();
|
||||
String orderBySql = findOrderBySql();
|
||||
|
||||
preFrom = trimSelectKeyword(preFrom);
|
||||
|
||||
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd,
|
||||
orderBySql, (distinctPos > -1));
|
||||
return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,12 +141,12 @@ class DRawSqlParser {
|
||||
return preWhereExprSql;
|
||||
}
|
||||
|
||||
private String findOrderByPrefixSql() {
|
||||
return (orderByPos < 1) ? null : sql.substring(orderByPos, orderByStmtPos);
|
||||
}
|
||||
|
||||
private String findOrderBySql() {
|
||||
if (orderByPos > -1) {
|
||||
int pos = orderByPos + ORDER_BY.length();
|
||||
return sql.substring(pos).trim();
|
||||
}
|
||||
return null;
|
||||
return (orderByStmtPos < 1) ? null : sql.substring(orderByStmtPos).trim();
|
||||
}
|
||||
|
||||
private String findPreHavingSql() {
|
||||
@@ -226,6 +225,11 @@ class DRawSqlParser {
|
||||
}
|
||||
|
||||
orderByPos = textParser.findWordLower("order", startOrderBy);
|
||||
if (orderByPos > 1) {
|
||||
// there might be keywords like siblings in between the order
|
||||
// and by so search for the by keyword explicitly
|
||||
orderByStmtPos = 2 + textParser.findWordLower("by", orderByPos);
|
||||
}
|
||||
}
|
||||
|
||||
private int findWhereExprPosition() {
|
||||
|
||||
@@ -152,20 +152,25 @@ public final class Ebean {
|
||||
|
||||
private ServerManager() {
|
||||
|
||||
// skipDefaultServer is set by EbeanServerFactory
|
||||
// ... when it is creating the primaryServer
|
||||
if (PrimaryServer.isSkip()) {
|
||||
// primary server being created by EbeanServerFactory
|
||||
// ... so we should not try and create it here
|
||||
logger.debug("PrimaryServer.isSkip()");
|
||||
try {
|
||||
// skipDefaultServer is set by EbeanServerFactory
|
||||
// ... when it is creating the primaryServer
|
||||
if (PrimaryServer.isSkip()) {
|
||||
// primary server being created by EbeanServerFactory
|
||||
// ... so we should not try and create it here
|
||||
logger.debug("PrimaryServer.isSkip()");
|
||||
|
||||
} else {
|
||||
// look to see if there is a default server defined
|
||||
String primaryName = PrimaryServer.getPrimaryServerName();
|
||||
logger.debug("primaryName:" + primaryName);
|
||||
if (primaryName != null && primaryName.trim().length() > 0) {
|
||||
primaryServer = getWithCreate(primaryName.trim());
|
||||
} else {
|
||||
// look to see if there is a default server defined
|
||||
String primaryName = PrimaryServer.getPrimaryServerName();
|
||||
logger.debug("primaryName:" + primaryName);
|
||||
if (primaryName != null && primaryName.trim().length() > 0) {
|
||||
primaryServer = getWithCreate(primaryName.trim());
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
logger.error("Error trying to create the default EbeanServer", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,7 +676,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 +1382,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
@@ -169,6 +169,16 @@ public interface ExpressionFactory {
|
||||
* In - property has a value in the collection of values.
|
||||
*/
|
||||
public Expression in(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* Exists expression
|
||||
*/
|
||||
public Expression exists(Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Not exists expression
|
||||
*/
|
||||
public Expression notExists(Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Id Equal to - ID property is equal to the value.
|
||||
|
||||
@@ -473,6 +473,16 @@ public interface ExpressionList<T> extends Serializable {
|
||||
* In - property has a value in the collection of values.
|
||||
*/
|
||||
public ExpressionList<T> in(String propertyName, Collection<?> values);
|
||||
|
||||
/**
|
||||
* Exists expression
|
||||
*/
|
||||
public ExpressionList<T> exists(Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Not exists expression
|
||||
*/
|
||||
public ExpressionList<T> notExists(Query<?> subQuery);
|
||||
|
||||
/**
|
||||
* Id IN a list of id values.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -46,10 +46,35 @@ class PrimaryServer {
|
||||
if (globalProperties == null) {
|
||||
globalProperties = PropertyMap.defaultProperties();
|
||||
}
|
||||
defaultServerName = globalProperties.getProperty("datasource.default");
|
||||
if (defaultServerName == null) {
|
||||
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
|
||||
defaultServerName = determineDefaultServerName();
|
||||
}
|
||||
return globalProperties;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine and return the default server name checking system environment variables and then global properties.
|
||||
*/
|
||||
private static String determineDefaultServerName() {
|
||||
|
||||
String defaultServerName = System.getProperty("datasource.default");
|
||||
if (isEmpty(defaultServerName)) {
|
||||
defaultServerName = System.getProperty("ebean.default.datasource");
|
||||
}
|
||||
if (isEmpty(defaultServerName)) {
|
||||
defaultServerName = globalProperties.getProperty("datasource.default");
|
||||
}
|
||||
if (isEmpty(defaultServerName)) {
|
||||
defaultServerName = globalProperties.getProperty("ebean.default.datasource");
|
||||
}
|
||||
return defaultServerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the string is null or empty.
|
||||
*/
|
||||
private static boolean isEmpty(String value) {
|
||||
return value == null || value.trim().length() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,54 +13,57 @@ import java.util.Set;
|
||||
* Example: Create the query using the API.
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* List<Order> orderList =
|
||||
* Ebean.find(Order.class)
|
||||
* .fetch("customer")
|
||||
* .fetch("details")
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Order> orderList =
|
||||
* ebeanServer.find(Order.class)
|
||||
* .fetch("customer")
|
||||
* .fetch("details")
|
||||
* .where()
|
||||
* .like("customer.name","rob%")
|
||||
* .gt("orderDate",lastWeek)
|
||||
* .orderBy("customer.id, id desc")
|
||||
* .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 =
|
||||
* " find order "
|
||||
* +" fetch customer "
|
||||
* +" fetch details "
|
||||
* +" where customer.name like :custName and orderDate > :minOrderDate "
|
||||
* +" order by customer.id, id desc "
|
||||
* +" limit 50 ";
|
||||
* " find order "
|
||||
* +" fetch customer "
|
||||
* +" fetch details "
|
||||
* +" where customer.name like :custName and orderDate > :minOrderDate "
|
||||
* +" order by customer.id, id desc "
|
||||
* +" limit 50 ";
|
||||
*
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, oql);
|
||||
* query.setParameter("custName", "Rob%");
|
||||
* query.setParameter("minOrderDate", lastWeek);
|
||||
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
|
||||
* query.setParameter("custName", "Rob%");
|
||||
* query.setParameter("minOrderDate", lastWeek);
|
||||
*
|
||||
* List<Order> 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<Order> query = Ebean.createNamedQuery(Order.class,"with.cust.and.details");
|
||||
* query.setParameter("custName", "Rob%");
|
||||
* query.setParameter("minOrderDate", lastWeek);
|
||||
* <pre>{@code
|
||||
*
|
||||
* Query<Order> query = ebeanServer.createNamedQuery(Order.class,"with.cust.and.details");
|
||||
* query.setParameter("custName", "Rob%");
|
||||
* query.setParameter("minOrderDate", lastWeek);
|
||||
*
|
||||
* List<Order> 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<Customer> query = Ebean.createQuery(Customer.class);
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Only fetch the customer id, name and status.
|
||||
* // This is described as a "Partial Object"
|
||||
* query.select("name, status");
|
||||
* query.where("lower(name) like :custname").setParameter("custname", "rob%");
|
||||
* 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<Customer> 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<Order> 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("customer", "name, phNumber");
|
||||
*
|
||||
* // ... also fetch the customers billing address (* = all properties)
|
||||
* query.fetch("customer.billingAddress", "*");
|
||||
* </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<Customer> query = Ebean.createQuery(Customer.class);
|
||||
*
|
||||
* // only fetch some of the properties of the customers
|
||||
* query.select("name, status");
|
||||
* List<Customer> 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<Customer> query = server.find(Customer.class)
|
||||
* .where().gt("id", 0)
|
||||
* .orderBy("id")
|
||||
* .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("-- visit " + 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<Customer> query = server.find(Customer.class)
|
||||
* .fetch("contacts", new FetchConfig().query(2))
|
||||
* .where().gt("id", 0)
|
||||
* .orderBy("id")
|
||||
* .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("-- visit " + 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<Product> query = Ebean.createQuery(Product.class);
|
||||
* query.setMapKey("sku");
|
||||
* Map<?, Product> 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("sku = ?")
|
||||
* .set(1, "aa113")
|
||||
* 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("details")
|
||||
* .fetch("details")
|
||||
* .findUnique();
|
||||
*
|
||||
* List<OrderDetail> 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 = "find order where status = :orderStatus";
|
||||
* String oql = "find order where status = :orderStatus";
|
||||
*
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, oql);
|
||||
* Query<Order> query = ebeanServer.find(Order.class, oql);
|
||||
*
|
||||
* // bind the named parameter
|
||||
* query.bind("orderStatus", OrderStatus.NEW);
|
||||
* List<Order> 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 = "where status = ? order by id desc";
|
||||
* String oql = "where status = ? order by id desc";
|
||||
*
|
||||
* Query<Order> query = Ebean.createQuery(Order.class, oql);
|
||||
* Query<Order> query = ebeanServer.createQuery(Order.class, oql);
|
||||
*
|
||||
* // bind the parameter
|
||||
* query.setParameter(1, OrderStatus.NEW);
|
||||
*
|
||||
* List<Order> 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<Order> query = Ebean.createQuery(Order.class);
|
||||
* Order order = query.setId(1).join("details").findUnique();
|
||||
* List<OrderDetail> 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<Order> query = Ebean.createQuery(Order.class, "top");
|
||||
* <pre>{@code
|
||||
*
|
||||
* Query<Order> query = ebeanServer.createQuery(Order.class, "top");
|
||||
* ...
|
||||
* if (...) {
|
||||
* query.where("status = :status and lower(customer.name) like :custName");
|
||||
* query.setParameter("status", Order.NEW);
|
||||
* query.setParameter("custName", "rob%");
|
||||
* 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<Order> newOrders =
|
||||
* Ebean.find(Order.class)
|
||||
* .where().eq("status", 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<Order> query = Ebean.createQuery(Order.class, "top");
|
||||
* ...
|
||||
* if (...) {
|
||||
* query.where()
|
||||
* .eq("status", Order.NEW)
|
||||
* .ilike("customer.name","rob%");
|
||||
* }
|
||||
* </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<Customer> list = Ebean
|
||||
* .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();
|
||||
* 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<ReportOrder> query = Ebean.createQuery(ReportOrder.class);
|
||||
* ...
|
||||
* if (...) {
|
||||
* query.having("score > :min");
|
||||
* query.setParameter("min", 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<Product> query = Ebean.createQuery(Product.class);
|
||||
*
|
||||
* // use sku for keys...
|
||||
* query.setMapKey("sku");
|
||||
*
|
||||
* Map<?,Product> 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.
|
||||
@@ -1113,4 +1230,9 @@ public interface Query<T> extends Serializable {
|
||||
* Return true if this query has forUpdate set.
|
||||
*/
|
||||
public boolean isForUpdate();
|
||||
|
||||
/**
|
||||
* Set root table alias.
|
||||
*/
|
||||
public Query<T> alias(String alias);
|
||||
}
|
||||
|
||||
@@ -12,19 +12,19 @@ package com.avaje.ebean;
|
||||
* QueryResultVisitor useful for processing large queries.
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* <pre>{@code
|
||||
*
|
||||
* Query<Customer> query = server.find(Customer.class)
|
||||
* .where().gt("id", 0)
|
||||
* .orderBy("id")
|
||||
* .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("-- visit " + customer);
|
||||
* System.out.println("-- visit " + customer);
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T>
|
||||
* the type of entity bean being queried.
|
||||
|
||||
@@ -2,12 +2,7 @@ package com.avaje.ebean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import com.avaje.ebean.util.CamelCaseHelper;
|
||||
|
||||
@@ -22,25 +17,25 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
* <b>Unparsed RawSql:</b>
|
||||
* </p>
|
||||
* <p>
|
||||
* When RawSql is created via RawSqlBuilder.unparsed(sql) then Ebean can not
|
||||
* When RawSql is created via {@link RawSqlBuilder#unparsed(String)} then Ebean can not
|
||||
* modify the SQL at all. It can't add any extra expressions into the SQL.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Parsed RawSql:</b>
|
||||
* </p>
|
||||
* <p>
|
||||
* When RawSql is created via RawSqlBuilder.parse(sql) then Ebean will parse the
|
||||
* When RawSql is created via {@link RawSqlBuilder#parse(String)} then Ebean will parse the
|
||||
* SQL and find places in the SQL where it can add extra where expressions, add
|
||||
* extra having expressions or replace the order by clause. If you want to
|
||||
* explicitly tell Ebean where these insertion points are you can place special
|
||||
* strings into your SQL (${where} or ${andWhere} and ${having} or
|
||||
* ${andHaving}).
|
||||
* strings into your SQL ({@code ${where}} or {@code ${andWhere}} and {@code ${having}} or
|
||||
* {@code ${andHaving})}.
|
||||
* </p>
|
||||
* <p>
|
||||
* If the SQL already includes a WHERE clause put in ${andWhere} in the location
|
||||
* If the SQL already includes a WHERE clause put in {@code ${andWhere}} in the location
|
||||
* you want Ebean to add any extra where expressions. If the SQL doesn't have a
|
||||
* WHERE clause put ${where} in instead. Similarly you can put in ${having} or
|
||||
* ${andHaving} where you want Ebean put add extra having expressions.
|
||||
* WHERE clause put {@code ${where}} in instead. Similarly you can put in {@code ${having}} or
|
||||
* {@code ${andHaving}} where you want Ebean put add extra having expressions.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Aggregates:</b>
|
||||
@@ -56,23 +51,19 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
* to hold the values for the aggregate functions (sum etc) and a @OneToOne
|
||||
* to Order.
|
||||
* </p>
|
||||
* <p>
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Example OrderAggregate</b>
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example OrderAggregate</h3>
|
||||
*
|
||||
* <pre class="code">
|
||||
* <pre>{@code
|
||||
* ...
|
||||
* // @Sql indicates to that this bean
|
||||
* // @Sql indicates to that this bean
|
||||
* // is based on RawSql rather than a table
|
||||
*
|
||||
* @Entity
|
||||
* @Sql
|
||||
* @Entity
|
||||
* @Sql
|
||||
* public class OrderAggregate {
|
||||
*
|
||||
* @OneToOne
|
||||
* @OneToOne
|
||||
* Order order;
|
||||
*
|
||||
* Double totalAmount;
|
||||
@@ -81,35 +72,38 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
*
|
||||
* // getters and setters
|
||||
* ...
|
||||
* </pre>
|
||||
* <p>
|
||||
* <b>Example 1:</b>
|
||||
* </p>
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Example 1:</h3>
|
||||
*
|
||||
* <pre class="code">
|
||||
* String sql = " select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount"
|
||||
* + " from o_order o"
|
||||
* + " join o_customer c on c.id = o.kcustomer_id "
|
||||
* + " join o_order_detail d on d.order_id = o.id " + " group by order_id, o.status ";
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = " select order_id, o.status, c.id, c.name, sum(d.order_qty*d.unit_price) as totalAmount"
|
||||
* + " from o_order o"
|
||||
* + " join o_customer c on c.id = o.kcustomer_id "
|
||||
* + " join o_order_detail d on d.order_id = o.id " + " group by order_id, o.status ";
|
||||
*
|
||||
* RawSql rawSql = RawSqlBuilder.parse(sql)
|
||||
* RawSql rawSql = RawSqlBuilder.parse(sql)
|
||||
* // map the sql result columns to bean properties
|
||||
* .columnMapping("order_id", "order.id").columnMapping("o.status", "order.status")
|
||||
* .columnMapping("c.id", "order.customer.id")
|
||||
* .columnMapping("c.name", "order.customer.name")
|
||||
* .columnMapping("order_id", "order.id")
|
||||
* .columnMapping("o.status", "order.status")
|
||||
* .columnMapping("c.id", "order.customer.id")
|
||||
* .columnMapping("c.name", "order.customer.name")
|
||||
* // we don't need to map this one due to the sql column alias
|
||||
* // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount")
|
||||
* // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount")
|
||||
* .create();
|
||||
*
|
||||
* Query<OrderAggregate> query = Ebean.find(OrderAggregate.class);
|
||||
* query.setRawSql(rawSql).where().gt("order.id", 0).having().gt("totalAmount", 20);
|
||||
* List<OrderAggregate> list = Ebean.find(OrderAggregate.class)
|
||||
* .setRawSql(rawSql)
|
||||
* .where().gt("order.id", 0)
|
||||
* .having().gt("totalAmount", 20)
|
||||
* .findList();
|
||||
*
|
||||
* List<OrderAggregate> list = query.findList();
|
||||
* </pre>
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* <b>Example 2:</b>
|
||||
* </p>
|
||||
* <h3>Example 2:</h3>
|
||||
*
|
||||
* <p>
|
||||
* The following example uses a FetchConfig().query() so that after the initial
|
||||
@@ -117,21 +111,59 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
* associated order status, orderDate along with the customer name.
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* String sql = " select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount "
|
||||
* + " from o_order_detail d"
|
||||
* + " group by order_id ";
|
||||
* <pre>{@code
|
||||
*
|
||||
* String sql = " select order_id, 'ignoreMe', sum(d.order_qty*d.unit_price) as totalAmount "
|
||||
* + " from o_order_detail d"
|
||||
* + " group by order_id ";
|
||||
*
|
||||
* RawSql rawSql = RawSqlBuilder.parse(sql).columnMapping("order_id", "order.id")
|
||||
* .columnMappingIgnore("'ignoreMe'").create();
|
||||
* RawSql rawSql = RawSqlBuilder.parse(sql)
|
||||
* .columnMapping("order_id", "order.id")
|
||||
* .columnMappingIgnore("'ignoreMe'")
|
||||
* .create();
|
||||
*
|
||||
* Query<OrderAggregate> query = Ebean.find(OrderAggregate.class);
|
||||
* query.setRawSql(rawSql).fetch("order", "status,orderDate", new FetchConfig().query())
|
||||
* .fetch("order.customer", "name").where()
|
||||
* .gt("order.id", 0).having().gt("totalAmount", 20).order().desc("totalAmount").setMaxRows(10);
|
||||
*
|
||||
* </pre>
|
||||
* List<OrderAggregate> orders = Ebean.find(OrderAggregate.class)
|
||||
* .setRawSql(rawSql)
|
||||
* .fetch("order", "status,orderDate", new FetchConfig().query())
|
||||
* .fetch("order.customer", "name")
|
||||
* .where().gt("order.id", 0)
|
||||
* .having().gt("totalAmount", 20)
|
||||
* .order().desc("totalAmount")
|
||||
* .setMaxRows(10)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*
|
||||
* <h3>Example 3: tableAliasMapping</h3>
|
||||
* <p>
|
||||
* Instead of mapping each column you can map each table alias to a path using tableAliasMapping().
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* String rs = "select o.id, o.status, c.id, c.name, "+
|
||||
* " d.id, d.order_qty, p.id, p.name " +
|
||||
* "from o_order o join o_customer c on c.id = o.kcustomer_id " +
|
||||
* "join o_order_detail d on d.order_id = o.id " +
|
||||
* "join o_product p on p.id = d.product_id " +
|
||||
* "where o.id <= :maxOrderId and p.id = :productId "+
|
||||
* "order by o.id, d.id asc";
|
||||
*
|
||||
* RawSql rawSql = RawSqlBuilder.parse(rs)
|
||||
* .tableAliasMapping("c", "customer")
|
||||
* .tableAliasMapping("d", "details")
|
||||
* .tableAliasMapping("p", "details.product")
|
||||
* .create();
|
||||
*
|
||||
* List<Order> ordersFromRaw = Ebean.find(Order.class)
|
||||
* .setRawSql(rawSql)
|
||||
* .setParameter("maxOrderId", 2)
|
||||
* .setParameter("productId", 1)
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Note that lazy loading also works with object graphs built with RawSql.
|
||||
* </p>
|
||||
@@ -222,6 +254,8 @@ public final class RawSql implements Serializable {
|
||||
|
||||
private final boolean andHavingExpr;
|
||||
|
||||
private final String orderByPrefix;
|
||||
|
||||
private final String orderBy;
|
||||
|
||||
private final boolean distinct;
|
||||
@@ -240,6 +274,7 @@ public final class RawSql implements Serializable {
|
||||
this.preWhere = null;
|
||||
this.andHavingExpr = false;
|
||||
this.andWhereExpr = false;
|
||||
this.orderByPrefix = null;
|
||||
this.orderBy = null;
|
||||
this.distinct = false;
|
||||
}
|
||||
@@ -248,8 +283,7 @@ public final class RawSql implements Serializable {
|
||||
* Construct for parsed SQL.
|
||||
*/
|
||||
protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr,
|
||||
String preHaving, boolean andHavingExpr,
|
||||
String orderBy, boolean distinct) {
|
||||
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
|
||||
|
||||
this.queryHashCode = queryHashCode;
|
||||
this.parsed = true;
|
||||
@@ -259,6 +293,7 @@ public final class RawSql implements Serializable {
|
||||
this.preWhere = preWhere;
|
||||
this.andHavingExpr = andHavingExpr;
|
||||
this.andWhereExpr = andWhereExpr;
|
||||
this.orderByPrefix = orderByPrefix;
|
||||
this.orderBy = orderBy;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
@@ -337,6 +372,14 @@ public final class RawSql implements Serializable {
|
||||
return andHavingExpr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'order by' keywords.
|
||||
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
|
||||
*/
|
||||
public String getOrderByPrefix() {
|
||||
return (orderByPrefix == null) ? "order by" : orderByPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL ORDER BY clause.
|
||||
*/
|
||||
@@ -429,9 +472,8 @@ public final class RawSql implements Serializable {
|
||||
for (Column c : dbColumnMap.values()) {
|
||||
pMap.put(c.getPropertyName(), c.getDbColumn());
|
||||
pcMap.put(c.getPropertyName(), c);
|
||||
|
||||
hc = 31 * hc + c.getPropertyName() == null ? 0 : c.getPropertyName().hashCode();
|
||||
hc = 31 * hc + c.getDbColumn() == null ? 0 : c.getDbColumn().hashCode();
|
||||
hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode());
|
||||
hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode());
|
||||
}
|
||||
this.propertyMap = Collections.unmodifiableMap(pMap);
|
||||
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
|
||||
@@ -531,6 +573,22 @@ public final class RawSql implements Serializable {
|
||||
return dbColumnMap.values().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
* <p>
|
||||
* For example modify all mappings with table alias "c" to have the path prefix "customer".
|
||||
* </p>
|
||||
*/
|
||||
public void tableAliasMapping(String tableAlias, String path) {
|
||||
|
||||
String startMatch = tableAlias+".";
|
||||
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
|
||||
if (entry.getKey().startsWith(startMatch)) {
|
||||
entry.getValue().tableAliasMapping(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Column of the RawSql that is mapped to a bean property (or ignored).
|
||||
*/
|
||||
@@ -612,10 +670,23 @@ public final class RawSql implements Serializable {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property name mapped to this db column.
|
||||
*/
|
||||
private void setPropertyName(String propertyName) {
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend the path to the property name.
|
||||
* <p/>
|
||||
* For example if path is "customer" then "name" becomes "customer.name".
|
||||
*/
|
||||
public void tableAliasMapping(String path) {
|
||||
if (path != null) {
|
||||
propertyName = path + "." + propertyName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +70,6 @@ public class RawSqlBuilder {
|
||||
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
|
||||
return new RawSqlBuilder(sql2, mapping);
|
||||
}
|
||||
|
||||
|
||||
private RawSqlBuilder(ResultSet resultSet, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.columnMapping = columnMapping;
|
||||
this.sql = null;
|
||||
}
|
||||
|
||||
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
|
||||
this.sql = sql;
|
||||
@@ -108,6 +101,17 @@ public class RawSqlBuilder {
|
||||
return columnMapping(dbColumn, IGNORE_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify any column mappings with the given table alias to have the path prefix.
|
||||
* <p>
|
||||
* For example modify all mappings with table alias "c" to have the path prefix "customer".
|
||||
* </p>
|
||||
*/
|
||||
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
|
||||
columnMapping.tableAliasMapping(tableAlias, path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the immutable RawSql object. Do this after all the column mapping
|
||||
* has been defined.
|
||||
@@ -122,6 +126,7 @@ public class RawSqlBuilder {
|
||||
protected Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 = "This is a simple test of the batch processing"
|
||||
* + " mode and the transaction execute batch method";
|
||||
* <pre>{@code
|
||||
*
|
||||
* String data = "This is a simple test of the batch processing"
|
||||
* + " mode and the transaction execute batch method";
|
||||
*
|
||||
* String[] da = data.split(" ");
|
||||
* String[] da = data.split(" ");
|
||||
*
|
||||
* String sql = "{call sp_t3(?,?)}";
|
||||
* 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("sp_test", 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 < 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
|
||||
* @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 {};
|
||||
|
||||
};
|
||||
|
||||
@@ -83,9 +83,17 @@ public interface BeanCollection<E> extends Serializable {
|
||||
public void setBeanCollectionTouched(BeanCollectionTouched notify);
|
||||
|
||||
/**
|
||||
* Set the loader that will be used to lazy/query load this collection.
|
||||
* Return true if the collection has been registered with the batch loading context.
|
||||
*/
|
||||
public void setLoader(int beanLoaderIndex, BeanCollectionLoader beanLoader);
|
||||
public boolean isRegisteredWithLoadContext();
|
||||
|
||||
/**
|
||||
* Set the loader that will be used to lazy/query load this collection.
|
||||
* <p>
|
||||
* This is effectively the batch loading context this collection is registered with.
|
||||
* </p>
|
||||
*/
|
||||
public void setLoader(BeanCollectionLoader beanLoader);
|
||||
|
||||
/**
|
||||
* Set to true if you want the BeanCollection to be treated as read only. This
|
||||
|
||||
@@ -47,9 +47,6 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private EntityBean owner;
|
||||
|
||||
/**
|
||||
* The parent bean by relationship (1-1 or 1-M).
|
||||
*/
|
||||
private EntityBean embeddedOwner;
|
||||
private int embeddedOwnerIndex;
|
||||
|
||||
@@ -175,7 +172,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent bean (by relationship).
|
||||
* Return the owning bean for an embedded bean.
|
||||
*/
|
||||
public Object getEmbeddedOwner() {
|
||||
return embeddedOwner;
|
||||
@@ -189,8 +186,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case for a OneToOne, Set the parent bean (by relationship). This is
|
||||
* the owner of a 1-1.
|
||||
* Set the embedded beans owning bean.
|
||||
*/
|
||||
public void setEmbeddedOwner(EntityBean parentBean, int embeddedOwnerIndex) {
|
||||
this.embeddedOwner = parentBean;
|
||||
|
||||
@@ -29,7 +29,10 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
|
||||
protected transient ExpressionList<?> filterMany;
|
||||
|
||||
protected int loaderIndex;
|
||||
/**
|
||||
* Flag set when registered with the batch loading context.
|
||||
*/
|
||||
protected boolean registeredWithLoadContext;
|
||||
|
||||
protected String ebeanServerName;
|
||||
|
||||
@@ -125,8 +128,12 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.beanCollectionTouched = notify;
|
||||
}
|
||||
|
||||
public void setLoader(int beanLoaderIndex, BeanCollectionLoader loader) {
|
||||
this.loaderIndex = beanLoaderIndex;
|
||||
public boolean isRegisteredWithLoadContext() {
|
||||
return registeredWithLoadContext;
|
||||
}
|
||||
|
||||
public void setLoader(BeanCollectionLoader loader) {
|
||||
this.registeredWithLoadContext = true;
|
||||
this.loader = loader;
|
||||
this.ebeanServerName = loader.getName();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -1670,7 +1721,9 @@ public class ServerConfig {
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt");
|
||||
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory");
|
||||
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager");
|
||||
|
||||
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
|
||||
classPathReaderClassName = p.get("classpathreader");
|
||||
|
||||
String jarsProp = p.get("search.jars", p.get("jars", null));
|
||||
if (jarsProp != null) {
|
||||
searchJars = getSearchJarsPackages(jarsProp);
|
||||
@@ -1689,8 +1742,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 +1778,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1789,4 +1830,16 @@ public class ServerConfig {
|
||||
}
|
||||
return hitList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the PersistBatch mode to use for 'batchOnCascade' taking into account if the database
|
||||
* platform supports getGeneratedKeys in batch mode.
|
||||
* <p>
|
||||
* Used to effectively turn off batchOnCascade for SQL Server - still allows explicit batch mode.
|
||||
* </p>
|
||||
*/
|
||||
public PersistBatch appliedPersistBatchOnCascade() {
|
||||
|
||||
return databasePlatform.isDisallowBatchOnCascade() ? PersistBatch.NONE : persistBatchOnCascade;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,370 +1,446 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
public class DatabasePlatform {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* DB specific DDL syntax.
|
||||
*/
|
||||
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
protected String columnAliasPrefix = "c";
|
||||
|
||||
protected String tableAliasPlaceHolder = "${ta}";
|
||||
|
||||
/**
|
||||
* Use a BackTick ` at the beginning and end of table or column names that you
|
||||
* want to use quoted identifiers for. The backticks get converted to the
|
||||
* appropriate characters in convertQuotedIdentifiers
|
||||
*/
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
|
||||
protected boolean selectCountWithAlias;
|
||||
|
||||
/**
|
||||
* If set then use the FORWARD ONLY hint when creating ResultSets for
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
public DatabasePlatform() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the DatabasePlatform.
|
||||
* <p>
|
||||
* "generic" is returned when no specific database platform has been set or
|
||||
* found.
|
||||
* </p>
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public DbEncrypt getDbEncrypt() {
|
||||
return dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public void setDbEncrypt(DbEncrypt dbEncrypt) {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
return dbTypeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL syntax for this platform.
|
||||
*
|
||||
* @return the db ddl syntax
|
||||
*/
|
||||
public DbDdlSyntax getDbDdlSyntax() {
|
||||
return dbDdlSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column alias prefix.
|
||||
*/
|
||||
public String getColumnAliasPrefix() {
|
||||
return columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column alias prefix.
|
||||
*/
|
||||
public void setColumnAliasPrefix(String columnAliasPrefix) {
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias placeholder.
|
||||
*/
|
||||
public String getTableAliasPlaceHolder() {
|
||||
return tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table alias placeholder.
|
||||
*/
|
||||
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Blob.
|
||||
* <p>
|
||||
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
|
||||
* example.
|
||||
* </p>
|
||||
*/
|
||||
public int getBlobDbType() {
|
||||
return blobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Clob.
|
||||
* <p>
|
||||
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
|
||||
* </p>
|
||||
*/
|
||||
public int getClobDbType() {
|
||||
return clobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
return treatEmptyStringsAsNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a compound ID in (...) type expression needs to be in
|
||||
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
|
||||
* ((?,?),(?,?),...);
|
||||
*/
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
|
||||
* findIterate() and findVisit() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for MySql when processing large results.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isForwardOnlyHintOnFindIterate() {
|
||||
return forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
|
||||
*/
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
return dbIdentity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SqlLimiter used to apply additional sql around a query to limit
|
||||
* its results.
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
return sqlLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
// Ignore null values e.g. schema name or catalog
|
||||
if (dbName != null && dbName.length() > 0) {
|
||||
if (dbName.charAt(0) == BACK_TICK) {
|
||||
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
|
||||
|
||||
String quotedName = getOpenQuote();
|
||||
quotedName += dbName.substring(1, dbName.length() - 1);
|
||||
quotedName += getCloseQuote();
|
||||
|
||||
return quotedName;
|
||||
|
||||
} else {
|
||||
logger.error("Missing backquote on [" + dbName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if select count against anonymous view requires an alias.
|
||||
*/
|
||||
public boolean isSelectCountWithAlias() {
|
||||
return selectCountWithAlias;
|
||||
}
|
||||
|
||||
public String completeSql(String sql, Query<?> query) {
|
||||
if (Boolean.TRUE.equals(query.isForUpdate())) {
|
||||
sql = withForUpdate(sql);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the like clause used by this database platform.
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
public class DatabasePlatform {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabasePlatform.class);
|
||||
|
||||
/**
|
||||
* Behavior used when ending a query only transaction (at read committed isolation level).
|
||||
*/
|
||||
public enum OnQueryOnly {
|
||||
|
||||
/**
|
||||
* Rollback the transaction.
|
||||
*/
|
||||
ROLLBACK,
|
||||
|
||||
/**
|
||||
* Just close the transaction. Valid at READ_COMMITTED isolation and preferred on some Databases
|
||||
* as a performance optimisation.
|
||||
*/
|
||||
CLOSE,
|
||||
|
||||
/**
|
||||
* Commit the transaction
|
||||
*/
|
||||
COMMIT
|
||||
}
|
||||
|
||||
/**
|
||||
* The behaviour used when ending a read only transaction at read committed isolation level.
|
||||
*/
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* DB specific DDL syntax.
|
||||
*/
|
||||
protected DbDdlSyntax dbDdlSyntax = new DbDdlSyntax();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
protected String columnAliasPrefix = "c";
|
||||
|
||||
protected String tableAliasPlaceHolder = "${ta}";
|
||||
|
||||
/**
|
||||
* Use a BackTick ` at the beginning and end of table or column names that you
|
||||
* want to use quoted identifiers for. The backticks get converted to the
|
||||
* appropriate characters in convertQuotedIdentifiers
|
||||
*/
|
||||
private static final char BACK_TICK = '`';
|
||||
|
||||
/**
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
|
||||
protected boolean selectCountWithAlias;
|
||||
|
||||
/**
|
||||
* If set then use the FORWARD ONLY hint when creating ResultSets for
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
|
||||
/**
|
||||
* Flag set for SQL Server due to lack of support of getGeneratedKeys in
|
||||
* batch mode (meaning for batch inserts you should explicitly turn off
|
||||
* getGeneratedKeys - joy).
|
||||
*/
|
||||
protected boolean disallowBatchOnCascade;
|
||||
|
||||
/**
|
||||
* Instantiates a new database platform.
|
||||
*/
|
||||
public DatabasePlatform() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the DatabasePlatform.
|
||||
* <p>
|
||||
* "generic" is returned when no specific database platform has been set or
|
||||
* found.
|
||||
* </p>
|
||||
*
|
||||
* @return the name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the behaviour to use when ending a read only transaction.
|
||||
*/
|
||||
public OnQueryOnly getOnQueryOnly() {
|
||||
return onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the behaviour to use when ending a read only transaction.
|
||||
*/
|
||||
public void setOnQueryOnly(OnQueryOnly onQueryOnly) {
|
||||
this.onQueryOnly = onQueryOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public DbEncrypt getDbEncrypt() {
|
||||
return dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DbEncrypt handler for this DB platform.
|
||||
*/
|
||||
public void setDbEncrypt(DbEncrypt dbEncrypt) {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
return dbTypeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL syntax for this platform.
|
||||
*
|
||||
* @return the db ddl syntax
|
||||
*/
|
||||
public DbDdlSyntax getDbDdlSyntax() {
|
||||
return dbDdlSyntax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column alias prefix.
|
||||
*/
|
||||
public String getColumnAliasPrefix() {
|
||||
return columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column alias prefix.
|
||||
*/
|
||||
public void setColumnAliasPrefix(String columnAliasPrefix) {
|
||||
this.columnAliasPrefix = columnAliasPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the table alias placeholder.
|
||||
*/
|
||||
public String getTableAliasPlaceHolder() {
|
||||
return tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the table alias placeholder.
|
||||
*/
|
||||
public void setTableAliasPlaceHolder(String tableAliasPlaceHolder) {
|
||||
this.tableAliasPlaceHolder = tableAliasPlaceHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
return closeQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
return openQuote;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
return booleanDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Blob.
|
||||
* <p>
|
||||
* This is typically Types.BLOB but for Postgres is Types.LONGVARBINARY for
|
||||
* example.
|
||||
* </p>
|
||||
*/
|
||||
public int getBlobDbType() {
|
||||
return blobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data type that should be used for Clob.
|
||||
* <p>
|
||||
* This is typically Types.CLOB but for Postgres is Types.VARCHAR.
|
||||
* </p>
|
||||
*/
|
||||
public int getClobDbType() {
|
||||
return clobDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
return treatEmptyStringsAsNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a compound ID in (...) type expression needs to be in
|
||||
* expanded form of (a=? and b=?) or (a=? and b=?) or ... rather than (a,b) in
|
||||
* ((?,?),(?,?),...);
|
||||
*/
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet TYPE_FORWARD_ONLY Hint should be used on
|
||||
* findIterate() and findVisit() PreparedStatements.
|
||||
* <p>
|
||||
* This specifically is required for MySql when processing large results.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isForwardOnlyHintOnFindIterate() {
|
||||
return forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if the ResultSet TYPE_FORWARD_ONLY Hint should be used by default on findIterate PreparedStatements.
|
||||
*/
|
||||
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
return dbIdentity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SqlLimiter used to apply additional sql around a query to limit
|
||||
* its results.
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
return sqlLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
// Ignore null values e.g. schema name or catalog
|
||||
if (dbName != null && dbName.length() > 0) {
|
||||
if (dbName.charAt(0) == BACK_TICK) {
|
||||
if (dbName.charAt(dbName.length() - 1) == BACK_TICK) {
|
||||
|
||||
String quotedName = getOpenQuote();
|
||||
quotedName += dbName.substring(1, dbName.length() - 1);
|
||||
quotedName += getCloseQuote();
|
||||
|
||||
return quotedName;
|
||||
|
||||
} else {
|
||||
logger.error("Missing backquote on [" + dbName + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if select count against anonymous view requires an alias.
|
||||
*/
|
||||
public boolean isSelectCountWithAlias() {
|
||||
return selectCountWithAlias;
|
||||
}
|
||||
|
||||
public String completeSql(String sql, Query<?> query) {
|
||||
if (Boolean.TRUE.equals(query.isForUpdate())) {
|
||||
sql = withForUpdate(sql);
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
protected String withForUpdate(String sql) {
|
||||
// silently assume the database does not support the "for update" clause.
|
||||
logger.info("it seems your database does not support the 'for update' clause");
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the like clause used by this database platform.
|
||||
* <p>
|
||||
* This may include an escape clause to disable a default escape character.
|
||||
*/
|
||||
public String getLikeClause() {
|
||||
return likeClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the persistBatchOnCascade setting should be ignored.
|
||||
* <p>
|
||||
* This is primarily for SQL Server which does not support getGeneratedKeys with jdbc batch mode
|
||||
* so can't really be transparently used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDisallowBatchOnCascade() {
|
||||
return disallowBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return the create sequence DDL.
|
||||
*/
|
||||
public String ddlCreateSequence(String sequenceName, int initialValue, int allocationSize) {
|
||||
|
||||
StringBuilder sb = new StringBuilder("create sequence ");
|
||||
sb.append(sequenceName);
|
||||
if (initialValue > 1) {
|
||||
sb.append(" start with ").append(initialValue);
|
||||
}
|
||||
if (allocationSize > 0 && allocationSize != 50) {
|
||||
// at this stage ignoring allocationSize 50 as this is the 'default' and
|
||||
// not consistent with the way Ebean batch fetches sequence values
|
||||
sb.append(" increment by ").append(allocationSize);
|
||||
}
|
||||
sb.append(";");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,30 +7,35 @@ import com.avaje.ebean.config.TableName;
|
||||
*/
|
||||
public class DbDdlSyntax {
|
||||
|
||||
private boolean renderIndexForFkey = true;
|
||||
protected boolean renderIndexForFkey = true;
|
||||
|
||||
private boolean inlinePrimaryKeyConstraint = false;
|
||||
protected boolean inlinePrimaryKeyConstraint = false;
|
||||
|
||||
private boolean addOneToOneUniqueContraint = true;
|
||||
protected boolean addOneToOneUniqueContraint = true;
|
||||
|
||||
private int maxConstraintNameLength = 32;
|
||||
protected int maxConstraintNameLength = 32;
|
||||
|
||||
private int columnNameWidth = 25;
|
||||
protected int columnNameWidth = 25;
|
||||
|
||||
private String dropTableCascade;
|
||||
private String dropIfExists;
|
||||
/**
|
||||
* Flag set when we want to disable constraints on each table (rather than globally).
|
||||
*/
|
||||
protected boolean dropKeyConstraints;
|
||||
|
||||
private String newLine = "\n";
|
||||
protected String dropTableCascade;
|
||||
protected String dropIfExists;
|
||||
|
||||
private String identity = "auto_increment";
|
||||
private String identitySuffix = "";
|
||||
protected String newLine = "\n";
|
||||
|
||||
private String pkPrefix = "pk_";
|
||||
protected String identity = "auto_increment";
|
||||
protected String identitySuffix = "";
|
||||
|
||||
private String disableReferentialIntegrity;
|
||||
private String enableReferentialIntegrity;
|
||||
protected String pkPrefix = "pk_";
|
||||
|
||||
private String foreignKeySuffix;
|
||||
protected String disableReferentialIntegrity;
|
||||
protected String enableReferentialIntegrity;
|
||||
|
||||
protected String foreignKeySuffix;
|
||||
|
||||
/**
|
||||
* Return the primary key name for a given bean descriptor.
|
||||
@@ -158,6 +163,20 @@ public class DbDdlSyntax {
|
||||
this.enableReferentialIntegrity = enableReferentialIntegrity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true to is constraints are disabled on each table.
|
||||
*/
|
||||
public boolean isDropKeyConstraints() {
|
||||
return dropKeyConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return some DDL to disable constraints on the given table.
|
||||
*/
|
||||
public String dropKeyConstraintPrefix(String tableName, String fkName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if indexes should be created for the foreign keys.
|
||||
*/
|
||||
@@ -188,6 +207,13 @@ public class DbDdlSyntax {
|
||||
this.dropIfExists = dropIfExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return prefix text that goes before drop table.
|
||||
*/
|
||||
public String dropTablePrefix(String tableName) {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cascade option for the drop table command.
|
||||
*/
|
||||
|
||||
@@ -17,8 +17,12 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
|
||||
public MsSqlServer2005Platform() {
|
||||
super();
|
||||
this.name = "mssqlserver2005";
|
||||
// effectively disable persistBatchOnCascade mode for SQL Server
|
||||
// due to lack of support for getGeneratedKeys in batch mode
|
||||
this.disallowBatchOnCascade = true;
|
||||
this.idInExpandedForm = true;
|
||||
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
|
||||
this.dbDdlSyntax.setIdentity("identity(1,1)");
|
||||
this.dbDdlSyntax = new MsDdlSyntax();
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsIdentity(true);
|
||||
@@ -28,6 +32,7 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
|
||||
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("numeric", 19));
|
||||
dbTypeMap.put(Types.REAL, new DbType("float(16)"));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float(32)"));
|
||||
@@ -45,4 +50,30 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* MS SQL Server specific DDL Syntax.
|
||||
*/
|
||||
public class MsDdlSyntax extends DbDdlSyntax {
|
||||
|
||||
MsDdlSyntax() {
|
||||
this.identity = "identity(1,1)";
|
||||
this.dropKeyConstraints = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return some DDL to disable constraints on the given table.
|
||||
*/
|
||||
public String dropKeyConstraintPrefix(String tableName, String fkName) {
|
||||
return "IF OBJECT_ID('"+fkName+"', 'F') IS NOT NULL";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return prefix text that goes before drop table.
|
||||
*/
|
||||
public String dropTablePrefix(String tableName) {
|
||||
return "IF OBJECT_ID('"+tableName+"', 'U') IS NOT NULL ";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class MySqlPlatform extends DatabasePlatform {
|
||||
|
||||
dbTypeMap.put(Types.BIT, new DbType("tinyint(1) default 0"));
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("tinyint(1) default 0"));
|
||||
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime"));
|
||||
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime(6)"));
|
||||
dbTypeMap.put(Types.CLOB, new MySqlClob());
|
||||
dbTypeMap.put(Types.BLOB, new MySqlBlob());
|
||||
dbTypeMap.put(Types.BINARY, new DbType("binary", 255));
|
||||
|
||||
@@ -1,67 +1,68 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
*/
|
||||
public class Oracle10Platform extends DatabasePlatform {
|
||||
|
||||
public Oracle10Platform() {
|
||||
super();
|
||||
this.name = "oracle";
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
|
||||
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
|
||||
|
||||
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
|
||||
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
|
||||
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
|
||||
|
||||
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
|
||||
dbDdlSyntax.setIdentity(null);
|
||||
dbDdlSyntax.setMaxConstraintNameLength(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Oracle10 and greater specific platform.
|
||||
*/
|
||||
public class Oracle10Platform extends DatabasePlatform {
|
||||
|
||||
public Oracle10Platform() {
|
||||
super();
|
||||
this.name = "oracle";
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Oracle
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.treatEmptyStringsAsNull = true;
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
booleanDbType = Types.INTEGER;
|
||||
dbTypeMap.put(Types.BOOLEAN, new DbType("number(1) default 0"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("number", 10));
|
||||
dbTypeMap.put(Types.BIGINT, new DbType("number", 19));
|
||||
dbTypeMap.put(Types.REAL, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("number", 19, 4));
|
||||
dbTypeMap.put(Types.SMALLINT, new DbType("number", 5));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("number", 3));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("number", 38));
|
||||
|
||||
dbTypeMap.put(Types.VARCHAR, new DbType("varchar2", 255));
|
||||
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("blob"));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("clob"));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("raw", 255));
|
||||
dbTypeMap.put(Types.BINARY, new DbType("raw", 255));
|
||||
|
||||
dbTypeMap.put(Types.TIME, new DbType("timestamp"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade constraints purge");
|
||||
dbDdlSyntax.setIdentity(null);
|
||||
dbDdlSyntax.setMaxConstraintNameLength(30);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new OracleSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,80 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* Uses serial types and getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.columnAliasPrefix = "as c";
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* Uses serial types and getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class PostgresPlatform extends DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Unique jdbc type id defined for hstore type.
|
||||
*/
|
||||
public static final int TYPE_HSTORE = 4001;
|
||||
|
||||
public PostgresPlatform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
this.columnAliasPrefix = "as c";
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
dbTypeMap.put(TYPE_HSTORE, new DbType("hstore"));
|
||||
|
||||
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
|
||||
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
|
||||
dbTypeMap.put(Types.TINYINT, new DbType("smallint"));
|
||||
dbTypeMap.put(Types.DECIMAL, new DbType("decimal", 38));
|
||||
|
||||
dbTypeMap.put(Types.BINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.VARBINARY, new DbType("bytea", false));
|
||||
|
||||
dbTypeMap.put(Types.BLOB, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.CLOB, new DbType("text"));
|
||||
dbTypeMap.put(Types.LONGVARBINARY, new DbType("bytea", false));
|
||||
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
|
||||
|
||||
dbDdlSyntax.setDropTableCascade("cascade");
|
||||
dbDdlSyntax.setDropIfExists("if exists");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Postgres specific sequence IdGenerator.
|
||||
*/
|
||||
@Override
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds,
|
||||
String seqName, int batchSize) {
|
||||
|
||||
return new PostgresSequenceIdGenerator(be, ds, seqName, batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String withForUpdate(String sql) {
|
||||
return sql + " for update";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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<Order> 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<OrderDetail> 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("HR");
|
||||
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>
|
||||
|
||||
|
||||
|
||||
@@ -118,15 +118,20 @@ class EJsonReader {
|
||||
private Object parseJson(JsonToken token) throws IOException {
|
||||
|
||||
if (token == null) {
|
||||
// no initial token so expect to read START_OBJECT or similar
|
||||
token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token) {
|
||||
return null;
|
||||
// if it is a simple value just return it
|
||||
switch (token) {
|
||||
case VALUE_NULL: return null;
|
||||
case VALUE_FALSE: return Boolean.FALSE;
|
||||
case VALUE_TRUE: return Boolean.TRUE;
|
||||
case VALUE_STRING: return parser.getText();
|
||||
case VALUE_NUMBER_INT: return parser.getLongValue();
|
||||
case VALUE_NUMBER_FLOAT: return parser.getDecimalValue();
|
||||
}
|
||||
}
|
||||
|
||||
stack = new Stack();
|
||||
// it is a object or array, process the first JsonToken
|
||||
stack = new Stack();
|
||||
processJsonToken(token);
|
||||
|
||||
// process the rest of the object or array
|
||||
|
||||
@@ -28,6 +28,13 @@ public interface JsonContext {
|
||||
*/
|
||||
public <T> T toBean(Class<T> rootType, Reader json) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json parser input into a Bean of a specific type.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json string input into a list of beans of a specific type.
|
||||
*
|
||||
@@ -42,6 +49,13 @@ public interface JsonContext {
|
||||
*/
|
||||
public <T> List<T> toList(Class<T> rootType, Reader json) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json parser input into a list of beans of a specific type.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Use the genericType to determine if this should be converted into a List or
|
||||
* bean.
|
||||
@@ -58,6 +72,14 @@ public interface JsonContext {
|
||||
*/
|
||||
public Object toObject(Type genericType, String json) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Use the genericType to determine if this should be converted into a List or
|
||||
* bean.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
public Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Return the bean or collection as JSON string.
|
||||
*
|
||||
@@ -109,7 +131,6 @@ public interface JsonContext {
|
||||
* With additional options.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public void toJson(Object value, Writer writer, JsonWriteOptions options) throws JsonIOException;
|
||||
@@ -119,7 +140,6 @@ public interface JsonContext {
|
||||
* Convert a bean or collection to json string.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public String toJson(Object value, JsonWriteOptions options) throws JsonIOException;
|
||||
|
||||
@@ -44,8 +44,11 @@ public interface LoadContext {
|
||||
|
||||
/**
|
||||
* Set the persistence context used by this query and future lazy loading.
|
||||
* <p>
|
||||
* Used by query iterator when processing large result sets.
|
||||
* </p>
|
||||
*/
|
||||
public void setPersistenceContext(PersistenceContext persistenceContext);
|
||||
public void resetPersistenceContext(PersistenceContext persistenceContext);
|
||||
|
||||
/**
|
||||
* Register a Bean for lazy loading.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -611,4 +611,9 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
* Return true if this query has been cancelled.
|
||||
*/
|
||||
public boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Return root table alias set by {@link #alias(String)} command.
|
||||
*/
|
||||
public String getAlias();
|
||||
}
|
||||
|
||||
@@ -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,31 @@ 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();
|
||||
|
||||
/**
|
||||
* Mark the transaction explicitly as not being query only.
|
||||
*/
|
||||
public void markNotQueryOnly();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
@@ -14,111 +14,110 @@ import com.avaje.ebeaninternal.api.TransactionEventTable;
|
||||
|
||||
public class DefaultCallableSql implements Serializable, SpiCallableSql {
|
||||
|
||||
private static final long serialVersionUID = 8984272253185424701L;
|
||||
private static final long serialVersionUID = 8984272253185424701L;
|
||||
|
||||
private transient final EbeanServer server;
|
||||
|
||||
/**
|
||||
* The callable sql.
|
||||
*/
|
||||
private String sql;
|
||||
private transient final EbeanServer server;
|
||||
|
||||
/**
|
||||
* To display in the transaction log to help identify the procedure.
|
||||
*/
|
||||
private String label;
|
||||
/**
|
||||
* The callable sql.
|
||||
*/
|
||||
private String sql;
|
||||
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* Holds the table modification information. On commit this information is
|
||||
* used to manage the cache etc.
|
||||
*/
|
||||
private TransactionEventTable transactionEvent = new TransactionEventTable();
|
||||
/**
|
||||
* To display in the transaction log to help identify the procedure.
|
||||
*/
|
||||
private String label;
|
||||
|
||||
private BindParams bindParameters = new BindParams();
|
||||
private int timeout;
|
||||
|
||||
/**
|
||||
* Create with callable sql.
|
||||
*/
|
||||
public DefaultCallableSql(EbeanServer server, String sql) {
|
||||
this.server = server;
|
||||
this.sql = sql;
|
||||
}
|
||||
/**
|
||||
* Holds the table modification information. On commit this information is
|
||||
* used to manage the cache etc.
|
||||
*/
|
||||
private final TransactionEventTable transactionEvent = new TransactionEventTable();
|
||||
|
||||
public void execute() {
|
||||
server.execute(this, null);
|
||||
}
|
||||
private final BindParams bindParameters = new BindParams();
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
/**
|
||||
* Create with callable sql.
|
||||
*/
|
||||
public DefaultCallableSql(EbeanServer server, String sql) {
|
||||
this.server = server;
|
||||
this.sql = sql;
|
||||
}
|
||||
|
||||
public CallableSql setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
public void execute() {
|
||||
server.execute(this, null);
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
public CallableSql setLabel(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CallableSql setTimeout(int secs) {
|
||||
this.timeout = secs;
|
||||
return this;
|
||||
}
|
||||
public int getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public CallableSql setSql(String sql) {
|
||||
this.sql = sql;
|
||||
return this;
|
||||
}
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public CallableSql bind(int position, Object value) {
|
||||
bindParameters.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
public CallableSql setTimeout(int secs) {
|
||||
this.timeout = secs;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CallableSql setParameter(int position, Object value) {
|
||||
bindParameters.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
public CallableSql setSql(String sql) {
|
||||
this.sql = sql;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CallableSql registerOut(int position, int type) {
|
||||
bindParameters.registerOut(position, type);
|
||||
return this;
|
||||
}
|
||||
public CallableSql bind(int position, Object value) {
|
||||
bindParameters.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object getObject(int position) {
|
||||
Param p = bindParameters.getParameter(position);
|
||||
return p.getOutValue();
|
||||
}
|
||||
|
||||
public boolean executeOverride(CallableStatement cstmt) throws SQLException {
|
||||
return false;
|
||||
}
|
||||
public CallableSql setParameter(int position, Object value) {
|
||||
bindParameters.setParameter(position, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CallableSql addModification(String tableName, boolean inserts, boolean updates,
|
||||
boolean deletes) {
|
||||
public CallableSql registerOut(int position, int type) {
|
||||
bindParameters.registerOut(position, type);
|
||||
return this;
|
||||
}
|
||||
|
||||
transactionEvent.add(tableName, inserts, updates, deletes);
|
||||
return this;
|
||||
}
|
||||
public Object getObject(int position) {
|
||||
Param p = bindParameters.getParameter(position);
|
||||
return p.getOutValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the TransactionEvent which holds the table modification
|
||||
* information for this CallableSql. This information is merged into the
|
||||
* transaction after the transaction is commited.
|
||||
*/
|
||||
public TransactionEventTable getTransactionEventTable() {
|
||||
return transactionEvent;
|
||||
}
|
||||
public boolean executeOverride(CallableStatement cstmt) throws SQLException {
|
||||
return false;
|
||||
}
|
||||
|
||||
public BindParams getBindParams() {
|
||||
return bindParameters;
|
||||
}
|
||||
public CallableSql addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
|
||||
|
||||
transactionEvent.add(tableName, inserts, updates, deletes);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the TransactionEvent which holds the table modification
|
||||
* information for this CallableSql. This information is merged into the
|
||||
* transaction after the transaction is commited.
|
||||
*/
|
||||
public TransactionEventTable getTransactionEventTable() {
|
||||
return transactionEvent;
|
||||
}
|
||||
|
||||
public BindParams getBindParams() {
|
||||
return bindParameters;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -174,8 +174,8 @@ public class DefaultServerFactory implements BootupEbeanManager {
|
||||
// warm the cache in 30 seconds
|
||||
long sleepMillis = 1000 * serverConfig.getCacheWarmingDelay();
|
||||
if (sleepMillis > 0) {
|
||||
Timer t = new Timer("EbeanCacheWarmer", true);
|
||||
t.schedule(new CacheWarmer(server), sleepMillis);
|
||||
Timer timer = new Timer("EbeanCacheWarmer", true);
|
||||
timer.schedule(new CacheWarmer(server, timer), sleepMillis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,12 +374,16 @@ public class DefaultServerFactory implements BootupEbeanManager {
|
||||
|
||||
private final EbeanServer server;
|
||||
|
||||
CacheWarmer(EbeanServer server) {
|
||||
private final Timer timer;
|
||||
|
||||
CacheWarmer(EbeanServer server, Timer timer) {
|
||||
this.server = server;
|
||||
this.timer = timer;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
server.runCacheWarming();
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
*/
|
||||
public void flushPersistenceContextOnIterate() {
|
||||
persistenceContext = new DefaultPersistenceContext();
|
||||
loadContext.setPersistenceContext(persistenceContext);
|
||||
loadContext.resetPersistenceContext(persistenceContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -76,6 +76,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private Integer beanHash;
|
||||
|
||||
/**
|
||||
* Flag set if this is a stateless update.
|
||||
*/
|
||||
private boolean statelessUpdate;
|
||||
|
||||
private boolean notifyCache;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
@@ -97,6 +102,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) {
|
||||
|
||||
@@ -119,6 +139,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
if (intercept.isNew()) {
|
||||
// 'stateless update' - set loaded properties as dirty
|
||||
intercept.setNewBeanForUpdate();
|
||||
statelessUpdate = true;
|
||||
}
|
||||
// Mark Mutable scalar properties (like Hstore) as dirty where necessary
|
||||
beanDescriptor.checkMutableProperties(intercept);
|
||||
@@ -127,6 +148,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 +218,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 +303,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 +466,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 +474,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 +507,26 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
String m = Message.msg("persist.conc2", "" + rowCount);
|
||||
throw new OptimisticLockException(m, null, bean);
|
||||
}
|
||||
switch (type) {
|
||||
case DELETE: postDelete(); break;
|
||||
case UPDATE: postUpdate(); break;
|
||||
default: // do nothing
|
||||
}
|
||||
}
|
||||
|
||||
public void postDelete() {
|
||||
/**
|
||||
* Clear the bean from the PersistenceContext (L1 cache) for stateless updates.
|
||||
*/
|
||||
private void postUpdate() {
|
||||
if (statelessUpdate) {
|
||||
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +542,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 +611,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,13 +76,6 @@ public final class PersistRequestCallableSql extends PersistRequest {
|
||||
public void setGeneratedKey(Object idValue) {
|
||||
}
|
||||
|
||||
/**
|
||||
* False for CallableSql.
|
||||
*/
|
||||
public boolean useGeneratedKeys() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post execute processing for the CallableSql.
|
||||
*/
|
||||
@@ -98,6 +91,8 @@ public final class PersistRequestCallableSql extends PersistRequest {
|
||||
|
||||
if (tableEvents != null && !tableEvents.isEmpty()) {
|
||||
transaction.getEvent().add(tableEvents);
|
||||
} else {
|
||||
transaction.markNotQueryOnly();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -141,7 +136,7 @@ public final class PersistRequestCallableSql extends PersistRequest {
|
||||
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
pos++;
|
||||
BindParams.Param param = (BindParams.Param) list.get(i);
|
||||
BindParams.Param param = list.get(i);
|
||||
if (param.isOutParam()) {
|
||||
Object outValue = cstmt.getObject(pos);
|
||||
param.setOutValue(outValue);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.ddl;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
@@ -16,23 +17,23 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
|
||||
|
||||
final FkeyPropertyVisitor pv;
|
||||
|
||||
public AddForeignKeysVisitor(DdlGenContext ctx) {
|
||||
public AddForeignKeysVisitor(boolean addMode, DdlGenContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.pv = new FkeyPropertyVisitor(this, ctx);
|
||||
this.pv = new FkeyPropertyVisitor(addMode, this, ctx);
|
||||
}
|
||||
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
if (!descriptor.isInheritanceRoot()){
|
||||
// ignore/skip if not a top level BeanDescriptor
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
if (!descriptor.isInheritanceRoot()) {
|
||||
// ignore/skip if not a top level BeanDescriptor
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
}
|
||||
|
||||
public void visitBegin() {
|
||||
}
|
||||
@@ -48,11 +49,17 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
|
||||
|
||||
public static class FkeyPropertyVisitor extends BaseTablePropertyVisitor {
|
||||
|
||||
/**
|
||||
* Set to false when generating drop foreign key statements.
|
||||
*/
|
||||
final boolean addMode;
|
||||
|
||||
final DdlGenContext ctx;
|
||||
|
||||
final AddForeignKeysVisitor parent;
|
||||
|
||||
public FkeyPropertyVisitor(AddForeignKeysVisitor parent, DdlGenContext ctx) {
|
||||
public FkeyPropertyVisitor(boolean addMode, AddForeignKeysVisitor parent, DdlGenContext ctx) {
|
||||
this.addMode = addMode;
|
||||
this.parent = parent;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
@@ -69,51 +76,59 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
|
||||
// Alter table o_address add Foreign Key (country_code) references o_country (code) on delete restrict on update restrict;
|
||||
|
||||
String baseTable = p.getBeanDescriptor().getBaseTable();
|
||||
|
||||
TableJoin tableJoin = p.getTableJoin();
|
||||
|
||||
TableJoinColumn[] columns = tableJoin.columns();
|
||||
|
||||
|
||||
String tableName = p.getBeanDescriptor().getBaseTable();
|
||||
String fkName = ctx.getDdlSyntax().getForeignKeyName(tableName, p.getName(), ctx.incrementFkCount());
|
||||
|
||||
ctx.write("alter table ").write(baseTable).write(" add ");
|
||||
DbDdlSyntax ddlSyntax = ctx.getDdlSyntax();
|
||||
String fkName = ddlSyntax.getForeignKeyName(tableName, p.getName(), ctx.incrementFkCount());
|
||||
|
||||
if (!addMode) {
|
||||
// look to generate drop foreign key statement
|
||||
String dropKeyConstraintPrefix = ddlSyntax.dropKeyConstraintPrefix(tableName, fkName);
|
||||
if (dropKeyConstraintPrefix != null && !dropKeyConstraintPrefix.isEmpty()) {
|
||||
ctx.write(dropKeyConstraintPrefix).write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
ctx.write("alter table ").write(baseTable).write(addOrDrop());
|
||||
if (fkName != null) {
|
||||
ctx.write("constraint ").write(fkName).write(" ");
|
||||
}
|
||||
ctx.write("foreign key (");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0){
|
||||
ctx.write(",");
|
||||
}
|
||||
ctx.write(columns[i].getLocalDbColumn());
|
||||
}
|
||||
ctx.write(")");
|
||||
if (addMode) {
|
||||
ctx.write("foreign key (");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
ctx.write(",");
|
||||
}
|
||||
ctx.write(columns[i].getLocalDbColumn());
|
||||
}
|
||||
ctx.write(")");
|
||||
|
||||
ctx.write(" references ");
|
||||
ctx.write(tableJoin.getTable());
|
||||
ctx.write(" (");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0){
|
||||
ctx.write(",");
|
||||
}
|
||||
ctx.write(columns[i].getForeignDbColumn());
|
||||
}
|
||||
ctx.write(")");
|
||||
ctx.write(" references ");
|
||||
ctx.write(tableJoin.getTable());
|
||||
ctx.write(" (");
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
ctx.write(",");
|
||||
}
|
||||
ctx.write(columns[i].getForeignDbColumn());
|
||||
}
|
||||
ctx.write(")");
|
||||
|
||||
String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix();
|
||||
if (fkeySuffix != null){
|
||||
ctx.write(" ").write(fkeySuffix);
|
||||
}
|
||||
String fkeySuffix = ctx.getDdlSyntax().getForeignKeySuffix();
|
||||
if (fkeySuffix != null) {
|
||||
ctx.write(" ").write(fkeySuffix);
|
||||
}
|
||||
}
|
||||
ctx.write(";").writeNewLine();
|
||||
|
||||
if (ctx.getDdlSyntax().isRenderIndexForFkey()){
|
||||
if (addMode && ddlSyntax.isRenderIndexForFkey()){
|
||||
|
||||
//create index idx_fk_o_address_ctry on o_address(country_code);
|
||||
ctx.write("create index ");
|
||||
|
||||
String idxName = ctx.getDdlSyntax().getIndexName(tableName, p.getName(), ctx.incrementIxCount());
|
||||
String idxName = ddlSyntax.getIndexName(tableName, p.getName(), ctx.incrementIxCount());
|
||||
if (idxName != null){
|
||||
ctx.write(idxName);
|
||||
}
|
||||
@@ -129,21 +144,27 @@ public class AddForeignKeysVisitor extends AbstractBeanVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
// not interested
|
||||
}
|
||||
/**
|
||||
* Return 'add' or 'drop' of foreign key.
|
||||
*/
|
||||
protected String addOrDrop() {
|
||||
return addMode ? " add " : " drop " ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompound(BeanPropertyCompound p) {
|
||||
// not interested
|
||||
}
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
// not interested
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
// not interested
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void visitCompound(BeanPropertyCompound p) {
|
||||
// not interested
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
// not interested
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,12 +36,14 @@ public class CreateSequenceVisitor implements BeanVisitor {
|
||||
+" as DatabasePlatform does not support sequences";
|
||||
logger.warn(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (descriptor.getSequenceName() != null) {
|
||||
ctx.write("create sequence ");
|
||||
ctx.write(descriptor.getSequenceName());
|
||||
ctx.write(";").writeNewLine().writeNewLine();
|
||||
String sequenceName = descriptor.getSequenceName();
|
||||
if (sequenceName != null) {
|
||||
int initialValue = descriptor.getSequenceInitialValue();
|
||||
int allocationSize = descriptor.getSequenceAllocationSize();
|
||||
String createSeq = ctx.getDbPlatform().ddlCreateSequence(sequenceName, initialValue, allocationSize);
|
||||
ctx.write(createSeq).writeNewLine().writeNewLine();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,6 +126,13 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
|
||||
DdlGenContext ctx = createContext();
|
||||
|
||||
if (ctx.getDdlSyntax().isDropKeyConstraints()) {
|
||||
// generate drop foreign key constraint statements (sql server joy)
|
||||
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(false, ctx);
|
||||
VisitorUtil.visit(server, fkeys);
|
||||
ctx.writeNewLine();
|
||||
}
|
||||
|
||||
DropTableVisitor drop = new DropTableVisitor(ctx);
|
||||
VisitorUtil.visit(server, drop);
|
||||
|
||||
@@ -146,7 +153,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
CreateSequenceVisitor createSequence = new CreateSequenceVisitor(ctx);
|
||||
VisitorUtil.visit(server, createSequence);
|
||||
|
||||
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(ctx);
|
||||
AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(true, ctx);
|
||||
VisitorUtil.visit(server, fkeys);
|
||||
|
||||
CreateIndexVisitor indexes = new CreateIndexVisitor(ctx);
|
||||
@@ -275,7 +282,6 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
} catch (Exception e) {
|
||||
if (expectErrors) {
|
||||
logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
|
||||
throw new RuntimeException(msg, e);
|
||||
|
||||
@@ -24,6 +24,7 @@ public class DropTableVisitor implements BeanVisitor {
|
||||
}
|
||||
|
||||
protected void writeDropTable(String tableName){
|
||||
ctx.write(ddlSyntax.dropTablePrefix(tableName));
|
||||
ctx.write("drop table ");
|
||||
if (ddlSyntax.getDropIfExists() != null){
|
||||
ctx.write(ddlSyntax.getDropIfExists()).write(" ");
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.SqlUpdate;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.ebean.annotation.ConcurrencyMode;
|
||||
@@ -35,11 +16,7 @@ import com.avaje.ebean.event.BeanPersistListener;
|
||||
import com.avaje.ebean.event.BeanQueryAdapter;
|
||||
import com.avaje.ebean.meta.MetaBeanInfo;
|
||||
import com.avaje.ebean.meta.MetaQueryPlanStatistic;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlan;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
|
||||
import com.avaje.ebeaninternal.api.*;
|
||||
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
|
||||
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
|
||||
@@ -50,13 +27,7 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
|
||||
import com.avaje.ebeaninternal.server.el.ElComparator;
|
||||
import com.avaje.ebeaninternal.server.el.ElComparatorCompound;
|
||||
import com.avaje.ebeaninternal.server.el.ElComparatorProperty;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.persist.DmlUtil;
|
||||
import com.avaje.ebeaninternal.server.el.*;
|
||||
import com.avaje.ebeaninternal.server.query.CQueryPlan;
|
||||
import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
@@ -68,6 +39,15 @@ import com.avaje.ebeaninternal.util.SortByClause;
|
||||
import com.avaje.ebeaninternal.util.SortByClause.Property;
|
||||
import com.avaje.ebeaninternal.util.SortByClauseParser;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Describes Beans including their deployment information.
|
||||
@@ -80,12 +60,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
|
||||
private final ConcurrentHashMap<HashQueryPlan, CQueryPlan> queryPlanCache = new ConcurrentHashMap<HashQueryPlan, CQueryPlan>();
|
||||
|
||||
private final ConcurrentHashMap<String, ElPropertyValue> elGetCache = new ConcurrentHashMap<String, ElPropertyValue>();
|
||||
private final ConcurrentHashMap<String, ElPropertyValue> elCache = new ConcurrentHashMap<String, ElPropertyValue>();
|
||||
|
||||
private final ConcurrentHashMap<String, ElPropertyDeploy> elDeployCache = new ConcurrentHashMap<String, ElPropertyDeploy>();
|
||||
|
||||
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<String, ElComparator<T>>();
|
||||
|
||||
private final ConcurrentHashMap<String, BeanFkeyProperty> fkeyMap = new ConcurrentHashMap<String, BeanFkeyProperty>();
|
||||
|
||||
public enum EntityType {
|
||||
ORM, EMBEDDED, SQL
|
||||
}
|
||||
@@ -112,6 +92,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
*/
|
||||
private final String sequenceName;
|
||||
|
||||
private final int sequenceInitialValue;
|
||||
|
||||
private final int sequenceAllocationSize;
|
||||
|
||||
/**
|
||||
* SQL used to return last inserted id. Used for Identity columns where
|
||||
* getGeneratedKeys is not supported.
|
||||
@@ -352,6 +336,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
this.idType = deploy.getIdType();
|
||||
this.idGenerator = deploy.getIdGenerator();
|
||||
this.sequenceName = deploy.getSequenceName();
|
||||
this.sequenceInitialValue = deploy.getSequenceInitialValue();
|
||||
this.sequenceAllocationSize = deploy.getSequenceAllocationSize();
|
||||
this.selectLastInsertedId = deploy.getSelectLastInsertedId();
|
||||
this.lazyFetchIncludes = InternString.intern(deploy.getLazyFetchIncludes());
|
||||
this.concurrencyMode = deploy.getConcurrencyMode();
|
||||
@@ -650,7 +636,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
* foreign keys don't require an extra join.
|
||||
*/
|
||||
public void add(BeanFkeyProperty fkey) {
|
||||
fkeyMap.put(fkey.getName(), fkey);
|
||||
elDeployCache.put(fkey.getName(), fkey);
|
||||
}
|
||||
|
||||
public void initialiseFkeys() {
|
||||
@@ -1036,6 +1022,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a raw expression for 'where parent id in ...' clause.
|
||||
*/
|
||||
public String getParentIdInExpr(int parentIdSize, String rawWhere) {
|
||||
String inClause = idBinder.getIdInValueExpr(parentIdSize);
|
||||
return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IdBinder which is helpful for handling the various types of Id.
|
||||
*/
|
||||
@@ -1391,7 +1385,15 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
* Get an Expression language Value object.
|
||||
*/
|
||||
public ElPropertyValue getElGetValue(String propName) {
|
||||
return getElPropertyValue(propName, false);
|
||||
ElPropertyValue elGetValue = elCache.get(propName);
|
||||
if (elGetValue != null) {
|
||||
return elGetValue;
|
||||
}
|
||||
elGetValue = buildElGetValue(propName, null, false);
|
||||
if (elGetValue != null) {
|
||||
elCache.put(propName, elGetValue);
|
||||
}
|
||||
return elGetValue;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1401,36 +1403,30 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
* </p>
|
||||
*/
|
||||
public ElPropertyDeploy getElPropertyDeploy(String propName) {
|
||||
ElPropertyDeploy fk = fkeyMap.get(propName);
|
||||
if (fk != null) {
|
||||
return fk;
|
||||
ElPropertyDeploy elProp = elDeployCache.get(propName);
|
||||
if (elProp != null) {
|
||||
return elProp;
|
||||
}
|
||||
return getElPropertyValue(propName, true);
|
||||
}
|
||||
|
||||
private ElPropertyValue getElPropertyValue(String propName, boolean propertyDeploy) {
|
||||
ElPropertyValue elGetValue = elGetCache.get(propName);
|
||||
if (elGetValue == null) {
|
||||
// need to build it potentially navigating the BeanDescriptors
|
||||
elGetValue = buildElGetValue(propName, null, propertyDeploy);
|
||||
if (elGetValue == null) {
|
||||
return null;
|
||||
}
|
||||
if (elGetValue instanceof BeanFkeyProperty) {
|
||||
fkeyMap.put(propName, (BeanFkeyProperty) elGetValue);
|
||||
} else {
|
||||
elGetCache.put(propName, elGetValue);
|
||||
}
|
||||
if (!propName.contains(".")) {
|
||||
// No period means simple property and no need to look for
|
||||
// foreign key properties (in order to avoid an extra join)
|
||||
elProp = getElGetValue(propName);
|
||||
} else {
|
||||
elProp = buildElGetValue(propName, null, true);
|
||||
}
|
||||
return elGetValue;
|
||||
if (elProp != null) {
|
||||
elDeployCache.put(propName, elProp);
|
||||
}
|
||||
return elProp;
|
||||
}
|
||||
|
||||
protected ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, boolean propertyDeploy) {
|
||||
|
||||
if (propertyDeploy && chain != null) {
|
||||
BeanFkeyProperty fk = fkeyMap.get(propName);
|
||||
if (fk != null) {
|
||||
return fk.create(chain.getExpression(), chain.isContainsMany());
|
||||
ElPropertyDeploy fk = elDeployCache.get(propName);
|
||||
if (fk != null && fk instanceof BeanFkeyProperty) {
|
||||
// propertyDeploy chain for foreign key column
|
||||
return ((BeanFkeyProperty)fk).create(chain.getExpression(), chain.isContainsMany());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,6 +1692,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
return sequenceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sequence initial value.
|
||||
*/
|
||||
public int getSequenceInitialValue() {
|
||||
return sequenceInitialValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sequence allocation size.
|
||||
*/
|
||||
public int getSequenceAllocationSize() {
|
||||
return sequenceAllocationSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL used to return the last inserted id.
|
||||
* <p>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.json.EJson;
|
||||
import com.avaje.ebeaninternal.server.text.json.WriteJson;
|
||||
import com.avaje.ebeaninternal.server.text.json.WriteJson.WriteBean;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
@@ -50,13 +51,18 @@ public class BeanDescriptorJsonHelp<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public T jsonRead(JsonParser parser, String path) throws IOException {
|
||||
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
return null;
|
||||
}
|
||||
if (JsonToken.START_OBJECT != token) {
|
||||
throw new JsonParseException("Unexpected token "+token+" - expecting start_object", parser.getCurrentLocation());
|
||||
|
||||
if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
|
||||
// start object token read by Jackson already
|
||||
} else {
|
||||
// check for null or start object
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
return null;
|
||||
}
|
||||
if (JsonToken.START_OBJECT != token) {
|
||||
throw new JsonParseException("Unexpected token "+token+" - expecting start_object", parser.getCurrentLocation());
|
||||
}
|
||||
}
|
||||
|
||||
if (desc.inheritInfo == null) {
|
||||
@@ -66,8 +72,7 @@ public class BeanDescriptorJsonHelp<T> {
|
||||
// check for the discriminator value to determine the correct sub type
|
||||
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
|
||||
|
||||
token = parser.nextToken();
|
||||
if (token != JsonToken.FIELD_NAME) {
|
||||
if (parser.nextToken() != JsonToken.FIELD_NAME) {
|
||||
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
|
||||
throw new JsonParseException(msg, parser.getCurrentLocation());
|
||||
}
|
||||
@@ -103,18 +108,16 @@ public class BeanDescriptorJsonHelp<T> {
|
||||
protected T jsonReadProperties(JsonParser parser, EntityBean bean) throws IOException {
|
||||
|
||||
do {
|
||||
|
||||
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.FIELD_NAME == event) {
|
||||
String key = parser.getCurrentName();
|
||||
BeanProperty p = desc.getBeanProperty(key);
|
||||
if (p != null) {
|
||||
p.jsonRead(parser, bean);
|
||||
|
||||
} else {
|
||||
// Object rawValue = EJson.parse(parser);
|
||||
// unknown property key ...
|
||||
// ctx.readUnmappedJson(propName);
|
||||
// unknown property ... read and ignore
|
||||
EJson.parse(parser);
|
||||
}
|
||||
|
||||
} else if (JsonToken.END_OBJECT == event) {
|
||||
|
||||
@@ -31,6 +31,10 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
this.placeHolder = calcPlaceHolder(prefix, dbColumn);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "prefix:"+prefix+" name:"+name+" dbColumn:"+dbColumn+" ph:"+placeHolder;
|
||||
}
|
||||
|
||||
public int getDeployOrder() {
|
||||
return deployOrder;
|
||||
}
|
||||
|
||||
+41
-45
@@ -1,8 +1,12 @@
|
||||
package com.avaje.ebeaninternal.server.deploy;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.PostPersist;
|
||||
import javax.persistence.PostRemove;
|
||||
@@ -21,7 +25,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
|
||||
/**
|
||||
* Helper that looks for methods annotated with lifecycle events and registers an adapter for them.
|
||||
* <p>
|
||||
* This includes PrePerist, PostPerist, PreUpdate, PostUpdate, PreRemove, PostRemove and PostLoad
|
||||
* This includes PrePersist, PostPersist, PreUpdate, PostUpdate, PreRemove, PostRemove and PostLoad
|
||||
* lifecycle events.
|
||||
* </p>
|
||||
*/
|
||||
@@ -36,7 +40,7 @@ public class BeanLifecycleAdapterFactory {
|
||||
|
||||
Method[] methods = deployDesc.getBeanType().getMethods();
|
||||
|
||||
MethodHolder methodHolder = new MethodHolder();
|
||||
MethodsHolder methodHolder = new MethodsHolder();
|
||||
|
||||
for (Method m : methods) {
|
||||
methodHolder.checkMethod(m);
|
||||
@@ -50,16 +54,16 @@ public class BeanLifecycleAdapterFactory {
|
||||
/**
|
||||
* Holds Methods for the lifecycle events.s
|
||||
*/
|
||||
private static class MethodHolder {
|
||||
private static class MethodsHolder {
|
||||
|
||||
private boolean hasListener;
|
||||
private Method preInsert;
|
||||
private Method postInsert;
|
||||
private Method preUpdate;
|
||||
private Method postUpdate;
|
||||
private Method preDelete;
|
||||
private Method postDelete;
|
||||
private Method postLoad;
|
||||
private List<Method> preInserts = new ArrayList<Method>();
|
||||
private List<Method> postInserts = new ArrayList<Method>();
|
||||
private List<Method> preUpdates = new ArrayList<Method>();
|
||||
private List<Method> postUpdates = new ArrayList<Method>();
|
||||
private List<Method> preDeletes = new ArrayList<Method>();
|
||||
private List<Method> postDeletes = new ArrayList<Method>();
|
||||
private List<Method> postLoads = new ArrayList<Method>();
|
||||
|
||||
private boolean hasListener() {
|
||||
return hasListener;
|
||||
@@ -67,37 +71,36 @@ public class BeanLifecycleAdapterFactory {
|
||||
|
||||
private void checkMethod(Method method) {
|
||||
if (method.isAnnotationPresent(PrePersist.class)) {
|
||||
preInsert = method;
|
||||
preInserts.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostPersist.class)) {
|
||||
postInsert = method;
|
||||
postInserts.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreUpdate.class)) {
|
||||
preUpdate = method;
|
||||
preUpdates.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostUpdate.class)) {
|
||||
postUpdate = method;
|
||||
postUpdates.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PreRemove.class)) {
|
||||
preDelete = method;
|
||||
preDeletes.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
if (method.isAnnotationPresent(PostRemove.class)) {
|
||||
postDelete = method;
|
||||
postDeletes.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
if (method.isAnnotationPresent(PostLoad.class)) {
|
||||
postLoad = method;
|
||||
postLoads.add(method);
|
||||
hasListener = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,9 +109,9 @@ public class BeanLifecycleAdapterFactory {
|
||||
*/
|
||||
private static class Adapter extends BeanPersistAdapter {
|
||||
|
||||
private final MethodHolder methodHolder;
|
||||
private final MethodsHolder methodHolder;
|
||||
|
||||
private Adapter(MethodHolder methodHolder) {
|
||||
private Adapter(MethodsHolder methodHolder) {
|
||||
this.methodHolder = methodHolder;
|
||||
}
|
||||
|
||||
@@ -121,66 +124,59 @@ public class BeanLifecycleAdapterFactory {
|
||||
private void invoke(Method method, Object bean) {
|
||||
try {
|
||||
method.invoke(bean);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error invoking lifecycle adapter", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new PersistenceException("Error invoking lifecycle method", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new PersistenceException("Error invoking lifecycle method", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void invoke(Method method, BeanPersistRequest<?> request) {
|
||||
invoke(method, request.getBean());
|
||||
private void invoke(List<Method> methods, BeanPersistRequest<?> request) {
|
||||
if (methods.isEmpty()) return;
|
||||
for (Method method : methods) {
|
||||
invoke(method, request.getBean());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preDelete(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preDelete != null) {
|
||||
invoke(methodHolder.preDelete, request);
|
||||
}
|
||||
invoke(methodHolder.preDeletes, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preInsert != null) {
|
||||
invoke(methodHolder.preInsert, request);
|
||||
}
|
||||
invoke(methodHolder.preInserts, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.preUpdate != null) {
|
||||
invoke(methodHolder.preUpdate, request);
|
||||
}
|
||||
invoke(methodHolder.preUpdates, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postDelete(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postDelete != null) {
|
||||
invoke(methodHolder.postDelete, request);
|
||||
}
|
||||
invoke(methodHolder.postDeletes, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInsert(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postInsert != null) {
|
||||
invoke(methodHolder.postInsert, request);
|
||||
}
|
||||
invoke(methodHolder.postInserts, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postUpdate(BeanPersistRequest<?> request) {
|
||||
if (methodHolder.postUpdate != null) {
|
||||
invoke(methodHolder.postUpdate, request);
|
||||
}
|
||||
invoke(methodHolder.postUpdates, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postLoad(Object bean, Set<String> includedProperties) {
|
||||
if (methodHolder.postLoad != null) {
|
||||
invoke(methodHolder.postLoad, bean);
|
||||
if (methodHolder.postLoads.isEmpty()) return;
|
||||
for (Method method : methodHolder.postLoads) {
|
||||
invoke(method, bean);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +225,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
return createElPropertyValue(propName, remainder, chain, propertyDeploy);
|
||||
}
|
||||
|
||||
public void buildSelectExpressionChain(String prefix, List<String> selectChain) {
|
||||
// do not add to the selectChain at the top level of the Many bean
|
||||
}
|
||||
|
||||
public SqlUpdate deleteByParentId(Object parentId, List<Object> parentIdist) {
|
||||
if (parentId != null) {
|
||||
return deleteByParentId(parentId);
|
||||
@@ -278,9 +282,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
query.setIncludeTableJoin(inverseJoin);
|
||||
}
|
||||
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
|
||||
String inClause = descriptor.getIdBinder().getIdInValueExpr(parentIds.size());
|
||||
|
||||
String expr = rawWhere + inClause;
|
||||
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
|
||||
|
||||
// Flatten the bind values if needed (embeddedId)
|
||||
List<Object> bindValues = getBindParentIds(parentIds);
|
||||
|
||||
@@ -287,7 +287,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if this a OneToOne property. Otherwise assumed ManyToOne.
|
||||
*/
|
||||
@@ -520,7 +519,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
@Override
|
||||
public void setValue(EntityBean bean, Object value) {
|
||||
super.setValue(bean, value);
|
||||
if (value instanceof EntityBean) {
|
||||
if (embedded && value instanceof EntityBean) {
|
||||
EntityBean embedded = (EntityBean)value;
|
||||
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
|
||||
}
|
||||
@@ -529,7 +528,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
@Override
|
||||
public void setValueIntercept(EntityBean bean, Object value) {
|
||||
super.setValueIntercept(bean, value);
|
||||
if (value instanceof EntityBean) {
|
||||
if (embedded && value instanceof EntityBean) {
|
||||
EntityBean embedded = (EntityBean)value;
|
||||
embedded._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ public final class TableJoin {
|
||||
*/
|
||||
private final TableJoinColumn[] columns;
|
||||
|
||||
/**
|
||||
* A hash that can be used with the query plan.
|
||||
*/
|
||||
private final int queryHash;
|
||||
|
||||
/**
|
||||
* Create a TableJoin.
|
||||
*/
|
||||
@@ -77,7 +82,26 @@ public final class TableJoin {
|
||||
BeanProperty prop = propMap.get(deployProps[i].getName());
|
||||
this.properties[i] = prop;
|
||||
}
|
||||
this.queryHash = calcQueryHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a hash value for adding to a query plan.
|
||||
*/
|
||||
private int calcQueryHash() {
|
||||
int hc = type.hashCode();
|
||||
hc = hc * 31 + (table == null ? 0 : table.hashCode());
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
hc = hc * 31 + columns[i].queryHash();
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash value for adding to a query plan.
|
||||
*/
|
||||
public int queryHash() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -21,7 +21,12 @@ public class TableJoinColumn {
|
||||
private final boolean insertable;
|
||||
|
||||
private final boolean updateable;
|
||||
|
||||
|
||||
/**
|
||||
* Hash for including in a query plan
|
||||
*/
|
||||
private final int queryHash;
|
||||
|
||||
/**
|
||||
* Create the pair.
|
||||
*/
|
||||
@@ -30,12 +35,23 @@ public class TableJoinColumn {
|
||||
this.foreignDbColumn = InternString.intern(deploy.getForeignDbColumn());
|
||||
this.insertable = deploy.isInsertable();
|
||||
this.updateable = deploy.isUpdateable();
|
||||
this.queryHash = hashOf(localDbColumn) * 31 + hashOf(foreignDbColumn);
|
||||
}
|
||||
|
||||
|
||||
private int hashOf(String value) {
|
||||
return (value == null) ? 0 : value.hashCode();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return localDbColumn+" = "+foreignDbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a hash for including in a query plan.
|
||||
*/
|
||||
public int queryHash() {
|
||||
return queryHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign database column name.
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Support joda time types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedInsertJodaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
|
||||
return prop.getValue(bean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
|
||||
return new LocalDateTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DateTime support.
|
||||
*/
|
||||
public static class DateTimeDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
|
||||
return new DateTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.generatedproperty;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import org.joda.time.LocalDateTime;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Support java.time DateTime types as GeneratedProperty.
|
||||
*/
|
||||
public class GeneratedUpdateJodaTime {
|
||||
|
||||
public static abstract class Base implements GeneratedProperty {
|
||||
|
||||
@Override
|
||||
public boolean includeInUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInAllUpdates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInInsert() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDDLNotNullable() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime support.
|
||||
*/
|
||||
public static class LocalDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
|
||||
return new LocalDateTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
|
||||
return new LocalDateTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OffsetDateTime support.
|
||||
*/
|
||||
public static class DateTimeDT extends Base {
|
||||
|
||||
@Override
|
||||
public Object getInsertValue(BeanProperty prop, EntityBean bean) {
|
||||
return new DateTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUpdateValue(BeanProperty prop, EntityBean bean) {
|
||||
return new DateTime();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -32,6 +32,11 @@ public class InsertTimestampFactory {
|
||||
map.put(OffsetDateTime.class, new GeneratedInsertJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedInsertJavaTime.ZonedDT());
|
||||
}
|
||||
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedInsertJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedInsertJodaTime.DateTimeDT());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setInsertTimestamp(DeployBeanProperty property) {
|
||||
|
||||
+4
@@ -32,6 +32,10 @@ public class UpdateTimestampFactory {
|
||||
map.put(OffsetDateTime.class, new GeneratedUpdateJavaTime.OffsetDT());
|
||||
map.put(ZonedDateTime.class, new GeneratedUpdateJavaTime.ZonedDT());
|
||||
}
|
||||
if (ClassUtil.isPresent("org.joda.time.LocalDateTime", this.getClass())) {
|
||||
map.put(org.joda.time.LocalDateTime.class, new GeneratedUpdateJodaTime.LocalDT());
|
||||
map.put(org.joda.time.DateTime.class, new GeneratedUpdateJodaTime.DateTimeDT());
|
||||
}
|
||||
}
|
||||
|
||||
public void setUpdateTimestamp(DeployBeanProperty property) {
|
||||
|
||||
@@ -25,6 +25,11 @@ public interface IdBinder {
|
||||
*/
|
||||
public void initialise();
|
||||
|
||||
/**
|
||||
* Return true if this is a compound key and must use expanded and or form.
|
||||
*/
|
||||
public boolean isIdInExpandedForm();
|
||||
|
||||
/**
|
||||
* Write the Id value to binary DataOuput.
|
||||
*/
|
||||
|
||||
@@ -43,17 +43,21 @@ public final class IdBinderEmbedded implements IdBinder {
|
||||
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
|
||||
}
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return idInExpandedForm;
|
||||
}
|
||||
|
||||
private String idInExpanded() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
sb.append("(");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(embIdProperty.getName());
|
||||
sb.append(idDesc.getBaseTableAlias());
|
||||
sb.append(".");
|
||||
sb.append(props[i].getName());
|
||||
sb.append(props[i].getDbColumn());
|
||||
sb.append("=?");
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
@@ -30,6 +30,10 @@ public final class IdBinderEmpty implements IdBinder {
|
||||
|
||||
}
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getOrderBy(String pathPrefix, boolean ascending) {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,11 @@ public final class IdBinderSimple implements IdBinder {
|
||||
public void initialise(){
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
public boolean isIdInExpandedForm() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getOrderBy(String pathPrefix, boolean ascending) {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
@@ -107,7 +107,7 @@ public class ImportedIdEmbedded implements ImportedId {
|
||||
if (embeddedId == null){
|
||||
for (int i = 0; i < imported.length; i++) {
|
||||
if (imported[i].owner.isUpdateable()) {
|
||||
request.bind(null, imported[i].foreignProperty, imported[i].localDbColumn);
|
||||
request.bind(null, imported[i].foreignProperty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class ImportedIdEmbedded implements ImportedId {
|
||||
for (int i = 0; i < imported.length; i++) {
|
||||
if (imported[i].owner.isUpdateable()) {
|
||||
Object scalarValue = imported[i].foreignProperty.getValue(embedded);
|
||||
request.bind(scalarValue, imported[i].foreignProperty, imported[i].localDbColumn);
|
||||
request.bind(scalarValue, imported[i].foreignProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ public class ImportedIdMultiple implements ImportedId {
|
||||
for (int i = 0; i < imported.length; i++) {
|
||||
if (imported[i].owner.isUpdateable()) {
|
||||
Object scalarValue = imported[i].foreignProperty.getValue(bean);
|
||||
request.bind(scalarValue, imported[i].foreignProperty, imported[i].localDbColumn);
|
||||
request.bind(scalarValue, imported[i].foreignProperty);
|
||||
}
|
||||
}
|
||||
// hmmm, not worrying about this just yet
|
||||
|
||||
@@ -135,7 +135,7 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
|
||||
if (bean != null){
|
||||
value = getIdValue(bean);
|
||||
}
|
||||
request.bind(value, foreignProperty, localDbColumn);
|
||||
request.bind(value, foreignProperty);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,10 @@ public class DeployBeanDescriptor<T> {
|
||||
*/
|
||||
private String sequenceName;
|
||||
|
||||
private int sequenceInitialValue;
|
||||
|
||||
private int sequenceAllocationSize;
|
||||
|
||||
/**
|
||||
* Used with Identity columns but no getGeneratedKeys support.
|
||||
*/
|
||||
@@ -263,6 +267,22 @@ public class DeployBeanDescriptor<T> {
|
||||
return entityType;
|
||||
}
|
||||
|
||||
public void setSequenceInitialValue(int sequenceInitialValue) {
|
||||
this.sequenceInitialValue = sequenceInitialValue;
|
||||
}
|
||||
|
||||
public void setSequenceAllocationSize(int sequenceAllocationSize) {
|
||||
this.sequenceAllocationSize = sequenceAllocationSize;
|
||||
}
|
||||
|
||||
public int getSequenceInitialValue() {
|
||||
return sequenceInitialValue;
|
||||
}
|
||||
|
||||
public int getSequenceAllocationSize() {
|
||||
return sequenceAllocationSize;
|
||||
}
|
||||
|
||||
public void add(DRawSqlMeta rawSqlMeta) {
|
||||
rawSqlMetas.put(rawSqlMeta.getName(), rawSqlMeta);
|
||||
if ("default".equals(rawSqlMeta.getName())) {
|
||||
|
||||
@@ -349,6 +349,8 @@ public class AnnotationFields extends AnnotationParser {
|
||||
if (sequenceGenerator.name().equals(genName)) {
|
||||
genName = sequenceGenerator.sequenceName();
|
||||
}
|
||||
descriptor.setSequenceInitialValue(sequenceGenerator.initialValue());
|
||||
descriptor.setSequenceAllocationSize(sequenceGenerator.allocationSize());
|
||||
}
|
||||
|
||||
GenerationType strategy = gen.strategy();
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import com.avaje.ebeaninternal.server.query.SplitName;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* A ElGetValue based on a chain of properties.
|
||||
@@ -41,9 +43,9 @@ public class ElPropertyChain implements ElPropertyValue {
|
||||
private final ScalarType<?> scalarType;
|
||||
|
||||
private final ElPropertyValue lastElPropertyValue;
|
||||
|
||||
|
||||
public ElPropertyChain(boolean containsMany, boolean embedded, String expression, ElPropertyValue[] chain) {
|
||||
|
||||
|
||||
this.containsMany = containsMany;
|
||||
this.chain = chain;
|
||||
this.expression = expression;
|
||||
@@ -51,19 +53,19 @@ public class ElPropertyChain implements ElPropertyValue {
|
||||
if (dotPos > -1){
|
||||
this.name = expression.substring(dotPos+1);
|
||||
if (embedded){
|
||||
int embPos = expression.lastIndexOf('.',dotPos-1);
|
||||
int embPos = expression.lastIndexOf('.',dotPos-1);
|
||||
this.prefix = embPos == -1 ? null : expression.substring(0, embPos);
|
||||
|
||||
|
||||
} else {
|
||||
this.prefix = expression.substring(0, dotPos);
|
||||
}
|
||||
} else {
|
||||
this.prefix = null;
|
||||
this.name = expression;
|
||||
}
|
||||
}
|
||||
|
||||
this.assocId = chain[chain.length-1].isAssocId();
|
||||
|
||||
|
||||
this.last = chain.length-1;
|
||||
this.lastBeanProperty = chain[chain.length-1].getBeanProperty();
|
||||
if (lastBeanProperty != null){
|
||||
@@ -77,6 +79,10 @@ public class ElPropertyChain implements ElPropertyValue {
|
||||
this.placeHolderEncrypted = getElPlaceHolder(prefix, lastElPropertyValue, true);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "expr:"+expression+" chain:"+ Arrays.toString(chain);
|
||||
}
|
||||
|
||||
private String getElPlaceHolder(String prefix, ElPropertyValue lastElPropertyValue, boolean encrypted) {
|
||||
if (prefix == null){
|
||||
return lastElPropertyValue.getElPlaceholder(encrypted);
|
||||
|
||||
@@ -241,6 +241,23 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
public Expression in(String propertyName, Collection<?> values) {
|
||||
return new InExpression(propertyName, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exists subquery
|
||||
*/
|
||||
@Override
|
||||
public Expression exists(Query<?> subQuery) {
|
||||
return new ExistsExpression((SpiQuery<?>) subQuery, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not exists subquery
|
||||
*/
|
||||
@Override
|
||||
public Expression notExists(Query<?> subQuery) {
|
||||
return new ExistsExpression((SpiQuery<?>) subQuery, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Id Equal to - ID property is equal to the value.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.ManyWhereJoins;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiExpression;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.query.CQuery;
|
||||
|
||||
public class ExistsExpression implements SpiExpression {
|
||||
|
||||
private static final long serialVersionUID = 666990277309851644L;
|
||||
|
||||
private final boolean not;
|
||||
private final SpiQuery<?> subQuery;
|
||||
|
||||
private transient CQuery<?> compiledSubQuery;
|
||||
|
||||
public ExistsExpression(SpiQuery<?> subQuery, boolean not) {
|
||||
this.subQuery = subQuery;
|
||||
this.not = not;
|
||||
}
|
||||
|
||||
public void queryAutoFetchHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(ExistsExpression.class).add(not);
|
||||
|
||||
subQuery.queryAutofetchHash(builder);
|
||||
}
|
||||
|
||||
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
|
||||
|
||||
// queryPlanHash executes prior to addSql() or addBindValues()
|
||||
// ... so compiledQuery will exist
|
||||
compiledSubQuery = compileSubQuery(request);
|
||||
|
||||
queryAutoFetchHash(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile/build the sub query.
|
||||
*/
|
||||
private CQuery<?> compileSubQuery(BeanQueryRequest<?> queryRequest) {
|
||||
|
||||
SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.getEbeanServer();
|
||||
return ebeanServer.compileQuery(subQuery, queryRequest.getTransaction());
|
||||
}
|
||||
|
||||
public int queryBindHash() {
|
||||
return subQuery.queryBindHash();
|
||||
}
|
||||
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
String subSelect = compiledSubQuery.getGeneratedSql();
|
||||
subSelect = subSelect.replace('\n', ' ');
|
||||
|
||||
if (not) {
|
||||
request.append(" not");
|
||||
}
|
||||
request.append(" exists (");
|
||||
request.append(subSelect);
|
||||
request.append(") ");
|
||||
}
|
||||
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
|
||||
List<Object> bindParams = compiledSubQuery.getPredicates().getWhereExprBindValues();
|
||||
|
||||
if (bindParams == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < bindParams.size(); i++) {
|
||||
request.addBindValue(bindParams.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
|
||||
// Nothing to do for exists expression
|
||||
}
|
||||
}
|
||||
@@ -361,6 +361,16 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
public ExpressionList<T> in(String propertyName, com.avaje.ebean.Query<?> subQuery) {
|
||||
return exprList.in(propertyName, subQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> exists(Query<?> subQuery) {
|
||||
return exprList.exists(subQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> notExists(Query<?> subQuery) {
|
||||
return exprList.exists(subQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> isNotNull(String propertyName) {
|
||||
|
||||
@@ -100,6 +100,6 @@ public abstract class DLoadBaseContext {
|
||||
|
||||
protected PersistenceContext getPersistenceContext() {
|
||||
return parent.getPersistenceContext();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
/**
|
||||
* Default implementation of LoadBeanContext.
|
||||
*
|
||||
*/
|
||||
public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext{
|
||||
|
||||
@@ -33,7 +32,17 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
this.bufferList = (!queryFetch) ? null : new ArrayList<DLoadBeanContext.LoadBuffer>();
|
||||
this.currentBuffer = createBuffer(firstBatchSize);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the buffers after a query iterator reset.
|
||||
*/
|
||||
public void clear() {
|
||||
if (bufferList != null) {
|
||||
bufferList.clear();
|
||||
}
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
|
||||
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
|
||||
|
||||
// propagate the readOnly state
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.loadcontext;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
import com.avaje.ebean.bean.ObjectGraphNode;
|
||||
import com.avaje.ebean.bean.ObjectGraphOrigin;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.bean.*;
|
||||
import com.avaje.ebeaninternal.api.LoadContext;
|
||||
import com.avaje.ebeaninternal.api.LoadSecondaryQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -21,6 +13,10 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
|
||||
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default implementation of LoadContext.
|
||||
*/
|
||||
@@ -220,8 +216,16 @@ public class DLoadContext implements LoadContext {
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
public void setPersistenceContext(PersistenceContext persistenceContext) {
|
||||
public void resetPersistenceContext(PersistenceContext persistenceContext) {
|
||||
this.persistenceContext = persistenceContext;
|
||||
// clear the load contexts for beans and beanCollections
|
||||
for (DLoadBeanContext beanContext : beanMap.values()) {
|
||||
beanContext.clear();
|
||||
}
|
||||
for (DLoadManyContext manyContext : manyMap.values()) {
|
||||
manyContext.clear();
|
||||
}
|
||||
this.rootBeanContext.clear();
|
||||
}
|
||||
|
||||
public void register(String path, EntityBeanIntercept ebi){
|
||||
|
||||
@@ -43,7 +43,17 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the buffers for a query iterator reset.
|
||||
*/
|
||||
public void clear() {
|
||||
if (bufferList != null) {
|
||||
bufferList.clear();
|
||||
}
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
|
||||
public void configureQuery(SpiQuery<?> query){
|
||||
|
||||
// propagate the readOnly state
|
||||
@@ -80,7 +90,7 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
currentBuffer.add(bc);
|
||||
bc.setLoader(0, currentBuffer);
|
||||
bc.setLoader(currentBuffer);
|
||||
}
|
||||
|
||||
public void loadSecondaryQuery(OrmQueryRequest<?> parentRequest) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,16 +2,17 @@ package com.avaje.ebeaninternal.server.persist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequest;
|
||||
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 +22,145 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
*/
|
||||
public class BatchedBeanHolder {
|
||||
|
||||
/**
|
||||
* The owning queue.
|
||||
*/
|
||||
private final BatchControl control;
|
||||
private static final Object DUMMY = new Object();
|
||||
|
||||
private final String shortDesc;
|
||||
/**
|
||||
* The owning queue.
|
||||
*/
|
||||
private final BatchControl control;
|
||||
|
||||
/**
|
||||
* The 'depth' which is used to determine the execution order.
|
||||
*/
|
||||
private final int order;
|
||||
private final String shortDesc;
|
||||
|
||||
/**
|
||||
* The list of bean insert requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> inserts;
|
||||
/**
|
||||
* The 'depth' which is used to determine the execution order.
|
||||
*/
|
||||
private final int order;
|
||||
|
||||
/**
|
||||
* The list of bean update requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> updates;
|
||||
/**
|
||||
* The list of bean insert requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> inserts;
|
||||
|
||||
/**
|
||||
* The list of bean delete requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> deletes;
|
||||
/**
|
||||
* The list of bean update requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> updates;
|
||||
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* The list of bean delete requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> deletes;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
/**
|
||||
* Set of beans in this batch. This is used to ensure that a single bean instance is not included
|
||||
* in the batch twice (two separate insert requests etc).
|
||||
*/
|
||||
private IdentityHashMap<Object,Object> persistedBeans = new IdentityHashMap<Object,Object>();
|
||||
|
||||
// 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();
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return shortDesc;
|
||||
}
|
||||
/**
|
||||
* Return the depth.
|
||||
*/
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
persistedBeans.clear();
|
||||
}
|
||||
|
||||
case UPDATE:
|
||||
if (updates == null) {
|
||||
updates = new ArrayList<PersistRequest>();
|
||||
}
|
||||
return updates;
|
||||
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 DELETE:
|
||||
if (deletes == null) {
|
||||
deletes = new ArrayList<PersistRequest>();
|
||||
}
|
||||
return deletes;
|
||||
/**
|
||||
* Add the request to the appropriate persist list.
|
||||
*/
|
||||
public int append(PersistRequestBean<?> request) {
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid type code " + request.getType());
|
||||
}
|
||||
}
|
||||
Object alreadyInBatch = persistedBeans.put(request.getEntityBean(), DUMMY);
|
||||
if (alreadyInBatch != null) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
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 persistedBeans.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;
|
||||
|
||||
+95
-93
@@ -1,93 +1,95 @@
|
||||
package com.avaje.ebeaninternal.server.persist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
|
||||
/**
|
||||
* Used for deletion of a partially populated bean where some cascade delete
|
||||
* properties where not loaded.
|
||||
* <p>
|
||||
* This bean effectively holds the foreign properties that where not loaded, and
|
||||
* helps fetch the foreign keys and delete the appropriate rows.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class DeleteUnloadedForeignKeys {
|
||||
|
||||
private final List<BeanPropertyAssocOne<?>> propList = new ArrayList<BeanPropertyAssocOne<?>>(4);
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final PersistRequestBean<?> request;
|
||||
|
||||
private EntityBean beanWithForeignKeys;
|
||||
|
||||
public DeleteUnloadedForeignKeys(SpiEbeanServer server, PersistRequestBean<?> request) {
|
||||
this.server = server;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return propList.isEmpty();
|
||||
}
|
||||
|
||||
public void add(BeanPropertyAssocOne<?> prop) {
|
||||
propList.add(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query fetching the missing (unloaded) foreign keys. We need to
|
||||
* fetch these key values before the parent bean is deleted.
|
||||
*/
|
||||
public void queryForeignKeys() {
|
||||
|
||||
BeanDescriptor<?> descriptor = request.getBeanDescriptor();
|
||||
SpiQuery<?> q = (SpiQuery<?>) server.createQuery(descriptor.getBeanType());
|
||||
|
||||
Object id = request.getBeanId();
|
||||
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
for (int i = 0; i < propList.size(); i++) {
|
||||
sb.append(propList.get(i).getName()).append(",");
|
||||
}
|
||||
|
||||
// run query in a separate persistence context
|
||||
q.setPersistenceContext(new DefaultPersistenceContext());
|
||||
q.setAutofetch(false);
|
||||
q.select(sb.toString());
|
||||
q.where().idEq(id);
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
if (t.isLogSummary()) {
|
||||
t.logSummary("-- Ebean fetching foreign key values for delete of " + descriptor.getName() + " id:" + id);
|
||||
}
|
||||
beanWithForeignKeys = (EntityBean)server.findUnique(q, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the rows relating to the foreign keys. These deletions occur after
|
||||
* the parent bean has been deleted.
|
||||
*/
|
||||
public void deleteCascade() {
|
||||
|
||||
for (int i = 0; i < propList.size(); i++) {
|
||||
BeanPropertyAssocOne<?> prop = propList.get(i);
|
||||
Object detailBean = prop.getValue(beanWithForeignKeys);
|
||||
|
||||
// if bean exists with a unique id then delete it
|
||||
if (detailBean != null && prop.hasId((EntityBean)detailBean)) {
|
||||
server.delete(detailBean, request.getTransaction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
package com.avaje.ebeaninternal.server.persist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.PersistenceContextScope;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
|
||||
/**
|
||||
* Used for deletion of a partially populated bean where some cascade delete
|
||||
* properties where not loaded.
|
||||
* <p>
|
||||
* This bean effectively holds the foreign properties that where not loaded, and
|
||||
* helps fetch the foreign keys and delete the appropriate rows.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class DeleteUnloadedForeignKeys {
|
||||
|
||||
private final List<BeanPropertyAssocOne<?>> propList = new ArrayList<BeanPropertyAssocOne<?>>(4);
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final PersistRequestBean<?> request;
|
||||
|
||||
private EntityBean beanWithForeignKeys;
|
||||
|
||||
public DeleteUnloadedForeignKeys(SpiEbeanServer server, PersistRequestBean<?> request) {
|
||||
this.server = server;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return propList.isEmpty();
|
||||
}
|
||||
|
||||
public void add(BeanPropertyAssocOne<?> prop) {
|
||||
propList.add(prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query fetching the missing (unloaded) foreign keys. We need to
|
||||
* fetch these key values before the parent bean is deleted.
|
||||
*/
|
||||
public void queryForeignKeys() {
|
||||
|
||||
BeanDescriptor<?> descriptor = request.getBeanDescriptor();
|
||||
SpiQuery<?> q = (SpiQuery<?>) server.createQuery(descriptor.getBeanType());
|
||||
|
||||
Object id = request.getBeanId();
|
||||
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
for (int i = 0; i < propList.size(); i++) {
|
||||
sb.append(propList.get(i).getName()).append(",");
|
||||
}
|
||||
|
||||
// run query in a separate persistence context
|
||||
q.setPersistenceContext(new DefaultPersistenceContext());
|
||||
q.setPersistenceContextScope(PersistenceContextScope.QUERY);
|
||||
q.setAutofetch(false);
|
||||
q.select(sb.toString());
|
||||
q.where().idEq(id);
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
if (t.isLogSummary()) {
|
||||
t.logSummary("-- Ebean fetching foreign key values for delete of " + descriptor.getName() + " id:" + id);
|
||||
}
|
||||
beanWithForeignKeys = (EntityBean)server.findUnique(q, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the rows relating to the foreign keys. These deletions occur after
|
||||
* the parent bean has been deleted.
|
||||
*/
|
||||
public void deleteCascade() {
|
||||
|
||||
for (int i = 0; i < propList.size(); i++) {
|
||||
BeanPropertyAssocOne<?> prop = propList.get(i);
|
||||
Object detailBean = prop.getValue(beanWithForeignKeys);
|
||||
|
||||
// if bean exists with a unique id then delete it
|
||||
if (detailBean != null && prop.hasId((EntityBean)detailBean)) {
|
||||
server.delete(detailBean, request.getTransaction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -176,18 +176,18 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
/**
|
||||
* Bind the value to the preparedStatement.
|
||||
*/
|
||||
public Object bind(Object value, BeanProperty prop, String propName) throws SQLException {
|
||||
return bindInternal(logLevelSql, value, prop, propName);
|
||||
public Object bind(Object value, BeanProperty prop) throws SQLException {
|
||||
return bindInternal(logLevelSql, value, prop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the value to the preparedStatement without logging.
|
||||
*/
|
||||
public Object bindNoLog(Object value, BeanProperty prop, String propName) throws SQLException {
|
||||
return bindInternal(false, value, prop, propName);
|
||||
public Object bindNoLog(Object value, BeanProperty prop) throws SQLException {
|
||||
return bindInternal(false, value, prop);
|
||||
}
|
||||
|
||||
private Object bindInternal(boolean log, Object value, BeanProperty prop, String propName) throws SQLException {
|
||||
private Object bindInternal(boolean log, Object value, BeanProperty prop) throws SQLException {
|
||||
|
||||
if (log) {
|
||||
if (prop.isLob()) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,10 +67,16 @@ public final class InsertMeta {
|
||||
} else {
|
||||
// insert sql for db identity or sequence insert
|
||||
this.concatinatedKey = false;
|
||||
this.identityDbColumns = new String[]{id.getIdentityColumn()};
|
||||
if (id.getIdentityColumn() == null) {
|
||||
this.identityDbColumns = new String[]{};
|
||||
this.supportsGetGeneratedKeys = false;
|
||||
this.selectLastInsertedId = null;
|
||||
} else {
|
||||
this.identityDbColumns = new String[]{id.getIdentityColumn()};
|
||||
this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys();
|
||||
this.selectLastInsertedId = desc.getSelectLastInsertedId();
|
||||
}
|
||||
this.sqlNullId = genSql(true);
|
||||
this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys();
|
||||
this.selectLastInsertedId = desc.getSelectLastInsertedId();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ public class BindableEncryptedProperty implements Bindable {
|
||||
// H2 encrypt function ... different parameter order
|
||||
request.bindNoLog(encryptKeyValue, Types.VARCHAR, prop.getName() + "=****");
|
||||
}
|
||||
request.bindNoLog(value, prop, prop.getName());
|
||||
request.bindNoLog(value, prop);
|
||||
|
||||
if (bindEncryptDataFirst){
|
||||
// MySql, Postgres, Oracle
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public final class BindableIdEmbedded implements BindableId {
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
|
||||
Object value = props[i].getValue(idValue);
|
||||
request.bind(value, props[i], props[i].getDbColumn());
|
||||
request.bind(value, props[i]);
|
||||
}
|
||||
|
||||
request.setIdValue(idValue);
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class BindableIdMap implements BindableId {
|
||||
for (int i = 0; i < uids.length; i++) {
|
||||
Object value = uids[i].getValue(bean);
|
||||
|
||||
request.bind(value, uids[i], uids[i].getName());
|
||||
request.bind(value, uids[i]);
|
||||
|
||||
// putting logicalType into map rather than
|
||||
// the dbType (which may have been converted).
|
||||
|
||||
@@ -61,7 +61,7 @@ public final class BindableIdScalar implements BindableId {
|
||||
|
||||
Object value = uidProp.getValue(bean);
|
||||
|
||||
request.bind(value, uidProp, uidProp.getName());
|
||||
request.bind(value, uidProp);
|
||||
|
||||
// used for summary logging
|
||||
request.setIdValue(value);
|
||||
|
||||
@@ -42,7 +42,7 @@ public class BindableProperty implements Bindable {
|
||||
if (bean != null) {
|
||||
value = prop.getValue(bean);
|
||||
}
|
||||
request.bind(value, prop, prop.getName());
|
||||
request.bind(value, prop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +54,6 @@ public class BindableProperty implements Bindable {
|
||||
if (bean != null) {
|
||||
value = prop.getValueObject(bean);
|
||||
}
|
||||
request.bind(value, prop, prop.getName());
|
||||
request.bind(value, prop);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user