mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
723ae6bffc | ||
|
|
33b173dd55 | ||
|
|
7966d74eb3 | ||
|
|
28cb2e210d | ||
|
|
1109d412e7 | ||
|
|
3e33ece65a | ||
|
|
1dd200fba2 | ||
|
|
f0ea977f91 | ||
|
|
e830d8efc7 | ||
|
|
0b2ba2a82e | ||
|
|
df526b8f3c | ||
|
|
8fe1f32e78 | ||
|
|
c5ea93fb8e | ||
|
|
dd83bc4d26 | ||
|
|
9b4b7a1a01 | ||
|
|
548fa745d6 | ||
|
|
7d83e3f49e | ||
|
|
974284bf8a | ||
|
|
b446f00cd8 | ||
|
|
a4095b4ae5 | ||
|
|
af6dbd0dbe | ||
|
|
db17ddd069 | ||
|
|
b6b85225e7 | ||
|
|
b57abfa732 | ||
|
|
4a4927b1f3 | ||
|
|
6ac34bec97 | ||
|
|
82725e6a98 | ||
|
|
a744c8b375 | ||
|
|
5e2a34de39 | ||
|
|
8a9ab39a01 | ||
|
|
515256abfd | ||
|
|
84e5a6145e | ||
|
|
1f5618d0e3 | ||
|
|
b1de821817 | ||
|
|
f4dceb97b0 |
@@ -1,12 +1,15 @@
|
||||
[](https://waffle.io/ebean-orm/avaje-ebeanorm)
|
||||
avaje-ebeanorm
|
||||
==============
|
||||
Release - 4.0.2 - May 19th: https://github.com/ebean-orm/avaje-ebeanorm/wiki/4.0.2-Release
|
||||
- Release - 4.0.2 - May 19th: https://github.com/ebean-orm/avaje-ebeanorm/wiki/4.0.2-Release
|
||||
- Release - 4.0.3 and 4.0.4 contained some bug fixes plus the Model and Finder objects.
|
||||
|
||||
|
||||
Maven Dependency
|
||||
----------------
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.0.2</version>
|
||||
<version>4.0.4</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<version>4.1.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -188,18 +188,7 @@
|
||||
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
|
||||
<version>4.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>main</id>
|
||||
<phase>process-classes</phase>
|
||||
<configuration>
|
||||
<classSource>target/classes</classSource>
|
||||
<packages>com.avaje.ebean.meta.**</packages>
|
||||
<transformArgs>debug=1</transformArgs>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>enhance</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<!-- Not going to enhance Model bean -->
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>process-test-classes</phase>
|
||||
@@ -230,8 +219,29 @@
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 4</doctitle>
|
||||
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
|
||||
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<phase>site</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
||||
|
||||
@@ -29,7 +29,7 @@ import javax.persistence.MappedSuperclass;
|
||||
* relatively nice clean way to write queries.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public class Model {
|
||||
public abstract class Model {
|
||||
|
||||
/**
|
||||
* Return the underlying 'default' EbeanServer.
|
||||
@@ -38,9 +38,36 @@ public class Model {
|
||||
* This provides full access to the API such as explicit transaction demarcation etc.
|
||||
*
|
||||
* <p>
|
||||
* TODO: Transaction example
|
||||
* Example:
|
||||
* <pre class="code">
|
||||
* Transaction transaction = Customer.db().beginTransaction();
|
||||
* try {
|
||||
*
|
||||
* // turn off cascade persist for this transaction
|
||||
* transaction.setPersistCascade(false);
|
||||
*
|
||||
* // extra control over jdbc batching for this transaction
|
||||
* transaction.setBatchGetGeneratedKeys(false);
|
||||
* transaction.setBatchMode(true);
|
||||
* transaction.setBatchSize(20);
|
||||
*
|
||||
* Customer customer = new Customer();
|
||||
* customer.setName("Roberto");
|
||||
* customer.save();
|
||||
*
|
||||
* Customer otherCustomer = new Customer();
|
||||
* otherCustomer.setName("Franko");
|
||||
* otherCustomer.save();
|
||||
*
|
||||
* transaction.commit();
|
||||
*
|
||||
* } finally {
|
||||
* transaction.end();
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public EbeanServer db() {
|
||||
public static EbeanServer db() {
|
||||
return Ebean.getServer(null);
|
||||
}
|
||||
|
||||
@@ -54,7 +81,7 @@ public class Model {
|
||||
* @param server
|
||||
* The name of the EbeanServer. If this is null then the default EbeanServer is returned.
|
||||
*/
|
||||
public EbeanServer db(String server) {
|
||||
public static EbeanServer db(String server) {
|
||||
return Ebean.getServer(server);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,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;
|
||||
@@ -135,18 +136,33 @@ import com.avaje.ebean.util.CamelCaseHelper;
|
||||
* Note that lazy loading also works with object graphs built with RawSql.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*
|
||||
*/
|
||||
public final class RawSql implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
protected RawSql(Sql sql, ColumnMapping columnMapping) {
|
||||
/**
|
||||
* Construct with a ResultSet and properties that the columns map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
* <p>
|
||||
* When a query executes this RawSql object then it will close the resultSet.
|
||||
*/
|
||||
public RawSql(ResultSet resultSet, String... propertyNames) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = null;
|
||||
this.columnMapping = new ColumnMapping(propertyNames);
|
||||
}
|
||||
|
||||
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
|
||||
this.resultSet = resultSet;
|
||||
this.sql = sql;
|
||||
this.columnMapping = columnMapping;
|
||||
}
|
||||
@@ -158,6 +174,14 @@ public final class RawSql implements Serializable {
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the resultSet if this is a ResultSet based RawSql.
|
||||
*/
|
||||
public ResultSet getResultSet() {
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column mapping for the SQL columns to bean properties.
|
||||
*/
|
||||
@@ -169,6 +193,9 @@ public final class RawSql implements Serializable {
|
||||
* Return the hash for this query.
|
||||
*/
|
||||
public int queryHash() {
|
||||
if (resultSet != null) {
|
||||
return 31 * columnMapping.queryHash();
|
||||
}
|
||||
return 31 * sql.queryHash() + columnMapping.queryHash();
|
||||
}
|
||||
|
||||
@@ -329,6 +356,7 @@ public final class RawSql implements Serializable {
|
||||
private final LinkedHashMap<String, Column> dbColumnMap;
|
||||
|
||||
private final Map<String, String> propertyMap;
|
||||
|
||||
private final Map<String, Column> propertyColumnMap;
|
||||
|
||||
private final boolean parsed;
|
||||
@@ -364,6 +392,26 @@ public final class RawSql implements Serializable {
|
||||
this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<String, Column>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for ResultSet use.
|
||||
*/
|
||||
protected ColumnMapping(String... propertyNames) {
|
||||
this.immutable = false;
|
||||
this.parsed = false;
|
||||
this.propertyMap = null;
|
||||
//this.propertyColumnMap = null;
|
||||
this.dbColumnMap = new LinkedHashMap<String, Column>();
|
||||
|
||||
int hc = 31;
|
||||
int pos = 0;
|
||||
for (String prop : propertyNames) {
|
||||
hc = 31 * hc + prop.hashCode();
|
||||
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
|
||||
}
|
||||
propertyColumnMap = dbColumnMap;
|
||||
this.queryHashCode = hc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an immutable ColumnMapping based on collected information.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
|
||||
import com.avaje.ebean.RawSql.ColumnMapping;
|
||||
import com.avaje.ebean.RawSql.Sql;
|
||||
|
||||
@@ -10,8 +12,6 @@ import com.avaje.ebean.RawSql.Sql;
|
||||
* named query.
|
||||
* </p>
|
||||
*
|
||||
* @author rbygrave
|
||||
*
|
||||
* @see RawSql
|
||||
*/
|
||||
public class RawSqlBuilder {
|
||||
@@ -21,10 +21,23 @@ public class RawSqlBuilder {
|
||||
*/
|
||||
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
|
||||
|
||||
private final ResultSet resultSet;
|
||||
|
||||
private final Sql sql;
|
||||
|
||||
private final ColumnMapping columnMapping;
|
||||
|
||||
/**
|
||||
* Create and return a RawSql object based on the resultSet and list of properties the columns in
|
||||
* the resultSet map to.
|
||||
* <p>
|
||||
* The properties listed in the propertyNames must be in the same order as the columns in the
|
||||
* resultSet.
|
||||
*/
|
||||
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
|
||||
return new RawSql(resultSet, propertyNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
|
||||
* modified - so no additional WHERE or HAVING expressions can be added to
|
||||
@@ -58,9 +71,17 @@ public class RawSqlBuilder {
|
||||
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;
|
||||
this.columnMapping = columnMapping;
|
||||
this.resultSet = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +113,7 @@ public class RawSqlBuilder {
|
||||
* has been defined.
|
||||
*/
|
||||
public RawSql create() {
|
||||
return new RawSql(sql, columnMapping.createImmutableCopy());
|
||||
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,4 +122,6 @@ public class RawSqlBuilder {
|
||||
protected Sql getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ColumnHstore {
|
||||
|
||||
}
|
||||
@@ -137,22 +137,6 @@ public interface BeanCollection<E> extends Serializable {
|
||||
*/
|
||||
public Collection<?> getActualEntries();
|
||||
|
||||
/**
|
||||
* Set to true if maxRows was hit and there are actually more rows available.
|
||||
* <p>
|
||||
* Can be used by client code that is paging through results using
|
||||
* setFirstRow() setMaxRows(). If this returns true then the client can
|
||||
* display a 'next' button etc.
|
||||
* </p>
|
||||
*/
|
||||
public boolean hasMoreRows();
|
||||
|
||||
/**
|
||||
* Set to true when maxRows is hit but there are actually more rows available.
|
||||
* This is set so that client code knows that there is more data available.
|
||||
*/
|
||||
public void setHasMoreRows(boolean hasMoreRows);
|
||||
|
||||
/**
|
||||
* return true if there are real rows held. Return false is this is using
|
||||
* Deferred fetch to lazy load the rows and the rows have not yet been
|
||||
|
||||
@@ -45,12 +45,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
*/
|
||||
protected final String propertyName;
|
||||
|
||||
/**
|
||||
* Flag set to true if rows are limited by firstRow maxRows and more rows
|
||||
* exist. For use by client to enable 'next' for paging.
|
||||
*/
|
||||
protected boolean hasMoreRows;
|
||||
|
||||
protected ModifyHolder<E> modifyHolder;
|
||||
|
||||
protected ModifyListenMode modifyListenMode;
|
||||
@@ -151,26 +145,6 @@ public abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if maxRows was hit and there are actually more rows available.
|
||||
* <p>
|
||||
* Can be used by client code that is paging through results using
|
||||
* setFirstRow() setMaxRows(). If this returns true then the client can
|
||||
* display a 'next' button etc.
|
||||
* </p>
|
||||
*/
|
||||
public boolean hasMoreRows() {
|
||||
return hasMoreRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when maxRows is hit but there are actually more rows available.
|
||||
* This is set so that client code knows that there is more data available.
|
||||
*/
|
||||
public void setHasMoreRows(boolean hasMoreRows) {
|
||||
this.hasMoreRows = hasMoreRows;
|
||||
}
|
||||
|
||||
protected void checkReadOnly() {
|
||||
if (readOnly) {
|
||||
String msg = "This collection is in ReadOnly mode";
|
||||
|
||||
@@ -153,7 +153,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanList ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -163,7 +163,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(list.size()).append("] ");
|
||||
sb.append("hasMoreRows[").append(hasMoreRows).append("] ");
|
||||
sb.append("list").append(list).append("");
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -154,7 +154,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanMap ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -164,7 +164,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(map.size()).append("]");
|
||||
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
|
||||
sb.append(" map").append(map);
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -143,7 +143,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer sb = new StringBuffer(50);
|
||||
sb.append("BeanSet ");
|
||||
if (isReadOnly()) {
|
||||
sb.append("readOnly ");
|
||||
@@ -153,7 +153,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
|
||||
} else {
|
||||
sb.append("size[").append(set.size()).append("]");
|
||||
sb.append(" hasMoreRows[").append(hasMoreRows).append("]");
|
||||
sb.append(" set").append(set);
|
||||
}
|
||||
return sb.toString();
|
||||
|
||||
@@ -57,6 +57,13 @@ public final class TableName {
|
||||
this.name = split[len - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a qualifiedTableName that might include a catalog and schema and just return the table name.
|
||||
*/
|
||||
public static String parse(String qualifiedTableName) {
|
||||
return new TableName(qualifiedTableName).getName();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getQualifiedName();
|
||||
}
|
||||
@@ -121,6 +128,18 @@ public final class TableName {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a catalog and schema prefix if they exist to the string builder.
|
||||
*/
|
||||
public void appendCatalogAndSchema(StringBuilder buffer) {
|
||||
if (catalog != null) {
|
||||
buffer.append(catalog).append(".");
|
||||
}
|
||||
if (schema != null) {
|
||||
buffer.append(schema).append(".");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is table name is valid i.e. it has at least a name.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
|
||||
/**
|
||||
* Used to support DB specific syntax for DDL generation.
|
||||
*/
|
||||
@@ -43,6 +45,17 @@ public class DbDdlSyntax {
|
||||
return pk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the column definition for an identity column.
|
||||
*/
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
String identity = getIdentity();
|
||||
if (identity != null && identity.length() > 0) {
|
||||
return columnDefn+" "+identity;
|
||||
}
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the identity clause for DB's that have identities.
|
||||
*/
|
||||
@@ -248,26 +261,29 @@ public class DbDdlSyntax {
|
||||
this.inlinePrimaryKeyConstraint = inlinePrimaryKeyConstraint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully index name.
|
||||
*/
|
||||
public String getIndexName(String table, String propName, int ixCount) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("ix_");
|
||||
buffer.append(table);
|
||||
buffer.append("_");
|
||||
buffer.append(propName);
|
||||
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("ix_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, ixCount);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns a fully qualified foreign key constraint name.
|
||||
*/
|
||||
public String getForeignKeyName(String table, String propName, int fkCount) {
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("fk_");
|
||||
buffer.append(table);
|
||||
buffer.append("_");
|
||||
buffer.append(propName);
|
||||
StringBuilder buffer = new StringBuilder(30);
|
||||
buffer.append("fk_").append(TableName.parse(table));
|
||||
buffer.append("_").append(propName);
|
||||
|
||||
addSuffix(buffer, fkCount);
|
||||
|
||||
@@ -276,15 +292,10 @@ public class DbDdlSyntax {
|
||||
|
||||
/**
|
||||
* Adds the suffix.
|
||||
*
|
||||
* @param buffer
|
||||
* the buffer
|
||||
* @param count
|
||||
* the count
|
||||
*/
|
||||
protected void addSuffix(StringBuilder buffer, int count) {
|
||||
final String suffixNr = Integer.toString(count);
|
||||
final int suffixLen = suffixNr.length() + 1;
|
||||
String suffixNr = Integer.toString(count);
|
||||
int suffixLen = suffixNr.length() + 1;
|
||||
|
||||
if (buffer.length() + suffixLen > maxConstraintNameLength) {
|
||||
buffer.setLength(maxConstraintNameLength - suffixLen);
|
||||
|
||||
@@ -17,22 +17,21 @@ public class LimitOffsetSqlLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(512);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(50 + dbSql.length());
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
if (maxRows > 0) {
|
||||
maxRows = maxRows + 1;
|
||||
}
|
||||
|
||||
if (maxRows > 0 || firstRow > 0) {
|
||||
sb.append(" ").append(NEW_LINE).append(LIMIT).append(" ").append(maxRows);
|
||||
sb.append(" ").append(LIMIT).append(" ").append(maxRows);
|
||||
if (firstRow > 0) {
|
||||
sb.append(" ").append(OFFSET).append(" ");
|
||||
sb.append(firstRow);
|
||||
|
||||
@@ -26,9 +26,7 @@ public class MsSqlServer2005SqlLimiter implements SqlLimiter {
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
// fetch 1 more than we return so that
|
||||
// we know if more rows are available
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
if (firstRow < 1) {
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.GlobalProperties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v8.3 specific platform.
|
||||
* <p>
|
||||
* No support for getGeneratedKeys.
|
||||
* </p>
|
||||
*/
|
||||
public class Postgres8Platform extends DatabasePlatform {
|
||||
|
||||
public Postgres8Platform() {
|
||||
super();
|
||||
this.name = "postgres";
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
|
||||
if (colAlias == null) {
|
||||
// Postgres requires the "as" keyword for column alias
|
||||
GlobalProperties.put("ebean.columnAliasPrefix", "as c");
|
||||
}
|
||||
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
public class PostgresDdlSyntax extends DbDdlSyntax {
|
||||
|
||||
/**
|
||||
* Map bigint, integer and smallint into their equivilent serial types.
|
||||
*/
|
||||
@Override
|
||||
public String getIdentityColumnDefn(String columnDefn) {
|
||||
|
||||
//smallserial, serial and bigserial
|
||||
if ("bigint".equalsIgnoreCase(columnDefn)) {
|
||||
return "bigserial";
|
||||
}
|
||||
if ("integer".equalsIgnoreCase(columnDefn)) {
|
||||
return "serial";
|
||||
}
|
||||
if ("smallint".equalsIgnoreCase(columnDefn)) {
|
||||
return "smallserial";
|
||||
}
|
||||
|
||||
return columnDefn;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,27 +4,37 @@ import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.GlobalProperties;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Postgres v8.3 specific platform.
|
||||
* Postgres v9 specific platform.
|
||||
* <p>
|
||||
* No support for getGeneratedKeys.
|
||||
* 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.dbDdlSyntax = new PostgresDdlSyntax();
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(false);
|
||||
this.dbIdentity.setIdType(IdType.SEQUENCE);
|
||||
// Use Identity and getGeneratedKeys
|
||||
this.dbIdentity.setIdType(IdType.IDENTITY);
|
||||
this.dbIdentity.setSupportsGetGeneratedKeys(true);
|
||||
this.dbIdentity.setSupportsSequence(true);
|
||||
|
||||
String colAlias = GlobalProperties.get("ebean.columnAliasPrefix", null);
|
||||
@@ -36,8 +46,8 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
this.openQuote = "\"";
|
||||
this.closeQuote = "\"";
|
||||
|
||||
// dbTypeMap.put(Types.BOOLEAN, new DbType("bit default 0"));
|
||||
|
||||
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"));
|
||||
|
||||
@@ -15,7 +15,7 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
*/
|
||||
private static final String ROW_NUMBER_AS = ") as rn, ";
|
||||
|
||||
final String rowNumberWindowAlias;
|
||||
private final String rowNumberWindowAlias;
|
||||
|
||||
/**
|
||||
* Specify the name of the rowNumberWindowAlias.
|
||||
@@ -30,16 +30,18 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
sb.append("select * from (").append(NEW_LINE);
|
||||
sb.append("select * from ( ");
|
||||
|
||||
sb.append("select ");
|
||||
if (request.isDistinct()) {
|
||||
@@ -50,9 +52,9 @@ public class RowNumberSqlLimiter implements SqlLimiter {
|
||||
sb.append(request.getDbOrderBy());
|
||||
sb.append(ROW_NUMBER_AS);
|
||||
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
sb.append(NEW_LINE).append(") ");
|
||||
sb.append(" ) ");
|
||||
sb.append(rowNumberWindowAlias);
|
||||
sb.append(" where ");
|
||||
if (firstRow > 0) {
|
||||
|
||||
@@ -35,38 +35,40 @@ public class RownumSqlLimiter implements SqlLimiter {
|
||||
// :MAX_ROW_TO_FETCH )
|
||||
// where rnum >= :MIN_ROW_TO_FETCH;
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
|
||||
int lastRow = request.getMaxRows();
|
||||
if (lastRow > 0) {
|
||||
lastRow = lastRow + firstRow + 1;
|
||||
lastRow = lastRow + firstRow;
|
||||
}
|
||||
|
||||
sb.append("select * ").append(NEW_LINE).append("from ( ");
|
||||
sb.append("select * from ( ");
|
||||
|
||||
sb.append("select ");
|
||||
if (useFirstRowsHint && request.getMaxRows() > 0) {
|
||||
sb.append("/*+ FIRST_ROWS(").append(request.getMaxRows() + 1).append(") */ ");
|
||||
sb.append("/*+ FIRST_ROWS(").append(request.getMaxRows()).append(") */ ");
|
||||
}
|
||||
|
||||
sb.append("rownum ").append(rnum).append(", a.* ").append(NEW_LINE);
|
||||
sb.append(" from (");//
|
||||
sb.append("rownum ").append(rnum).append(", a.* ");
|
||||
sb.append(" from (");
|
||||
|
||||
sb.append(" select ");
|
||||
if (request.isDistinct()) {
|
||||
sb.append("distinct ");
|
||||
}
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
sb.append(NEW_LINE).append(" ) a ");
|
||||
sb.append(NEW_LINE).append(" ) a ");
|
||||
if (lastRow > 0) {
|
||||
sb.append(NEW_LINE).append(" where rownum <= ").append(lastRow);
|
||||
sb.append(" where rownum <= ").append(lastRow);
|
||||
}
|
||||
sb.append(NEW_LINE).append(" ) ");
|
||||
sb.append(" ) ");
|
||||
if (firstRow > 0) {
|
||||
sb.append(NEW_LINE).append("where ");
|
||||
sb.append(" where ");
|
||||
sb.append(rnum).append(" > ").append(firstRow);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,12 @@ public class SqlAnywhereLimiter implements SqlLimiter {
|
||||
|
||||
public SqlLimitResponse limit(SqlLimitRequest request) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbSql = request.getDbSql();
|
||||
|
||||
StringBuilder sb = new StringBuilder(60 + dbSql.length());
|
||||
|
||||
int firstRow = request.getFirstRow();
|
||||
int maxRows = request.getMaxRows();
|
||||
if (maxRows > 0) {
|
||||
// fetch 1 more than we return so that
|
||||
// we know if more rows are available
|
||||
maxRows = maxRows + 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* SELECT TOP xx START AT xx ... FROM ...
|
||||
@@ -31,7 +28,7 @@ public class SqlAnywhereLimiter implements SqlLimiter {
|
||||
if (firstRow > 0) {
|
||||
sb.append("start at ").append(firstRow + 1).append(" ");
|
||||
}
|
||||
sb.append(request.getDbSql());
|
||||
sb.append(dbSql);
|
||||
|
||||
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ import com.avaje.ebean.Query;
|
||||
/**
|
||||
* The request object for the query that can have sql limiting applied to it
|
||||
* (such as a LIMIT OFFSET clause).
|
||||
*
|
||||
* @author rob
|
||||
*/
|
||||
public interface SqlLimitRequest {
|
||||
|
||||
|
||||
@@ -128,17 +128,17 @@ public abstract class BeanRequest {
|
||||
return transaction.getInternalConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SQL should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSql() {
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SUMMARY information should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSummary() {
|
||||
return transaction.isLogSummary();
|
||||
}
|
||||
/**
|
||||
* Return true if SQL should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSql() {
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if SUMMARY information should be logged for this transaction.
|
||||
*/
|
||||
public boolean isLogSummary() {
|
||||
return transaction.isLogSummary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,16 +53,16 @@ public class DatabasePlatformFactory {
|
||||
private DatabasePlatform byDatabaseName(String dbName) throws SQLException {
|
||||
|
||||
dbName = dbName.toLowerCase();
|
||||
if (dbName.equals("postgres83")) {
|
||||
if (dbName.equals("postgres") || dbName.equals("postgres9")) {
|
||||
return new PostgresPlatform();
|
||||
}
|
||||
if (dbName.equals("postgres8") || dbName.equals("postgres83")) {
|
||||
return new Postgres8Platform();
|
||||
}
|
||||
if (dbName.equals("oracle9")) {
|
||||
return new Oracle9Platform();
|
||||
}
|
||||
if (dbName.equals("oracle10")) {
|
||||
return new Oracle10Platform();
|
||||
}
|
||||
if (dbName.equals("oracle")) {
|
||||
if (dbName.equals("oracle") || dbName.equals("oracle10")) {
|
||||
return new Oracle10Platform();
|
||||
}
|
||||
if (dbName.equals("sqlserver2005")) {
|
||||
|
||||
@@ -108,7 +108,10 @@ public class DefaultBeanLoader {
|
||||
BeanDescriptor<?> desc = ctx.getBeanDescriptor();
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
|
||||
|
||||
String orderBy = many.getLazyFetchOrderBy();
|
||||
if (orderBy != null) {
|
||||
query.orderBy(orderBy);
|
||||
}
|
||||
query.setLazyLoadForParents(idList, many);
|
||||
many.addWhereParentIdIn(query, idList);
|
||||
|
||||
|
||||
@@ -362,9 +362,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
* Log the SQL if the logLevel is appropriate.
|
||||
*/
|
||||
public void logSql(String sql) {
|
||||
if (transaction.isLogSql()) {
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,74 +34,75 @@ import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
|
||||
*/
|
||||
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
|
||||
|
||||
private final BeanManager<T> beanManager;
|
||||
private final BeanManager<T> beanManager;
|
||||
|
||||
private final BeanDescriptor<T> beanDescriptor;
|
||||
private final BeanDescriptor<T> beanDescriptor;
|
||||
|
||||
private final BeanPersistListener<T> beanPersistListener;
|
||||
private final BeanPersistListener<T> beanPersistListener;
|
||||
|
||||
/**
|
||||
* For per post insert update delete control.
|
||||
*/
|
||||
private final BeanPersistController controller;
|
||||
/**
|
||||
* For per post insert update delete control.
|
||||
*/
|
||||
private final BeanPersistController controller;
|
||||
|
||||
/**
|
||||
/**
|
||||
* The bean being persisted.
|
||||
*/
|
||||
private final T bean;
|
||||
|
||||
private final EntityBean entityBean;
|
||||
|
||||
/**
|
||||
* The associated intercept.
|
||||
*/
|
||||
private final EntityBeanIntercept intercept;
|
||||
private final T bean;
|
||||
|
||||
/**
|
||||
* The parent bean for unidirectional save.
|
||||
*/
|
||||
private final Object parentBean;
|
||||
private final EntityBean entityBean;
|
||||
|
||||
private final boolean dirty;
|
||||
/**
|
||||
* The associated intercept.
|
||||
*/
|
||||
private final EntityBeanIntercept intercept;
|
||||
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
/**
|
||||
* The parent bean for unidirectional save.
|
||||
*/
|
||||
private final Object parentBean;
|
||||
|
||||
/**
|
||||
* The unique id used for logging summary.
|
||||
*/
|
||||
private Object idValue;
|
||||
private final boolean dirty;
|
||||
|
||||
/**
|
||||
* Hash value used to handle cascade delete both ways in a relationship.
|
||||
*/
|
||||
private Integer beanHash;
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
|
||||
private boolean notifyCache;
|
||||
/**
|
||||
* The unique id used for logging summary.
|
||||
*/
|
||||
private Object idValue;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
|
||||
private final Set<String> dirtyPropertyNames;
|
||||
/**
|
||||
* Hash value used to handle cascade delete both ways in a relationship.
|
||||
*/
|
||||
private Integer beanHash;
|
||||
|
||||
private boolean notifyCache;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
|
||||
/**
|
||||
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
|
||||
* appropriate caches are updated in that case.
|
||||
*/
|
||||
private boolean updatedManysOnly;
|
||||
|
||||
/**
|
||||
* Many properties that were cascade saved (and hence might need caches updated later).
|
||||
*/
|
||||
private boolean updatedManysOnly;
|
||||
|
||||
/**
|
||||
* Many properties that were cascade saved (and hence might need caches updated later).
|
||||
*/
|
||||
private List<BeanPropertyAssocMany<?>> updatedManys;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr,
|
||||
SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type) {
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean)bean;
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean) bean;
|
||||
this.intercept = entityBean._ebean_getIntercept();
|
||||
this.beanManager = mgr;
|
||||
this.beanDescriptor = mgr.getBeanDescriptor();
|
||||
this.beanPersistListener = beanDescriptor.getPersistListener();
|
||||
this.beanManager = mgr;
|
||||
this.beanDescriptor = mgr.getBeanDescriptor();
|
||||
this.beanPersistListener = beanDescriptor.getPersistListener();
|
||||
this.bean = bean;
|
||||
this.parentBean = parentBean;
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
|
||||
if (PersistRequest.Type.DETERMINE != type) {
|
||||
this.type = type;
|
||||
@@ -110,30 +111,27 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
this.type = beanDescriptor.isInsertMode(intercept) ? Type.INSERT : Type.UPDATE;
|
||||
this.persistCascade = t.isPersistCascade();
|
||||
}
|
||||
|
||||
if (this.type == Type.UPDATE && intercept.isNew() ) {
|
||||
// 'stateless update' - set bean up for doing update
|
||||
intercept.setNewBeanForUpdate();
|
||||
|
||||
if (this.type == Type.UPDATE) {
|
||||
if (intercept.isNew()) {
|
||||
// 'stateless update' - set loaded properties as dirty
|
||||
intercept.setNewBeanForUpdate();
|
||||
}
|
||||
// Mark Mutable scalar properties (like Hstore) as dirty where necessary
|
||||
beanDescriptor.checkMutableProperties(intercept);
|
||||
}
|
||||
|
||||
// derive the set of property names now as we will pass them to a beanPersistListener later
|
||||
this.dirtyPropertyNames = (beanPersistListener == null) ? null : intercept.getDirtyPropertyNames();
|
||||
|
||||
this.bean = bean;
|
||||
this.parentBean = parentBean;
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
|
||||
this.dirty = intercept.isDirty();
|
||||
}
|
||||
this.dirty = intercept.isDirty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an insert request.
|
||||
*/
|
||||
/**
|
||||
* Return true if this is an insert request.
|
||||
*/
|
||||
public boolean isInsert() {
|
||||
return Type.INSERT == type;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public Set<String> getLoadedProperties() {
|
||||
return intercept.getLoadedPropertyNames();
|
||||
}
|
||||
@@ -148,18 +146,18 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return intercept.getDirtyValues();
|
||||
}
|
||||
|
||||
public boolean isNotify(TransactionEvent txnEvent) {
|
||||
this.notifyCache = beanDescriptor.isCacheNotify();
|
||||
return notifyCache || isNotifyPersistListener();
|
||||
}
|
||||
public boolean isNotify(TransactionEvent txnEvent) {
|
||||
this.notifyCache = beanDescriptor.isCacheNotify();
|
||||
return notifyCache || isNotifyPersistListener();
|
||||
}
|
||||
|
||||
public boolean isNotifyPersistListener() {
|
||||
return beanPersistListener != null;
|
||||
}
|
||||
public boolean isNotifyPersistListener() {
|
||||
return beanPersistListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify/Update the local L2 cache after the transaction has successfully committed.
|
||||
*/
|
||||
/**
|
||||
* Notify/Update the local L2 cache after the transaction has successfully committed.
|
||||
*/
|
||||
public void notifyCache() {
|
||||
if (notifyCache) {
|
||||
switch (type) {
|
||||
@@ -178,414 +176,416 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
|
||||
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
|
||||
|
||||
beanPersistMap.add(beanDescriptor, type, idValue);
|
||||
}
|
||||
beanPersistMap.add(beanDescriptor, type, idValue);
|
||||
}
|
||||
|
||||
public boolean notifyLocalPersistListener() {
|
||||
if (beanPersistListener == null) {
|
||||
return false;
|
||||
public boolean notifyLocalPersistListener() {
|
||||
if (beanPersistListener == null) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
return beanPersistListener.inserted(bean);
|
||||
} else {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
return beanPersistListener.inserted(bean);
|
||||
|
||||
case UPDATE:
|
||||
return beanPersistListener.updated(bean, dirtyPropertyNames);
|
||||
case UPDATE:
|
||||
return beanPersistListener.updated(bean, intercept.getDirtyPropertyNames());
|
||||
|
||||
case DELETE:
|
||||
return beanPersistListener.deleted(bean);
|
||||
case DELETE:
|
||||
return beanPersistListener.deleted(bean);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isParent(Object o) {
|
||||
return o == parentBean;
|
||||
}
|
||||
public boolean isParent(Object o) {
|
||||
return o == parentBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this bean has been already been persisted
|
||||
* (inserted or updated) in this transaction.
|
||||
*/
|
||||
public boolean isRegisteredBean() {
|
||||
return transaction.isRegisteredBean(bean);
|
||||
}
|
||||
|
||||
public void unRegisterBean() {
|
||||
transaction.unregisterBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash used to register the bean with the transaction.
|
||||
* <p>
|
||||
* Takes into account the class type and id value.
|
||||
* </p>
|
||||
*/
|
||||
private Integer getBeanHash() {
|
||||
if (beanHash == null) {
|
||||
Object id = beanDescriptor.getId(entityBean);
|
||||
int hc = 31 * bean.getClass().getName().hashCode();
|
||||
if (id != null) {
|
||||
hc += id.hashCode();
|
||||
}
|
||||
beanHash = Integer.valueOf(hc);
|
||||
}
|
||||
return beanHash;
|
||||
}
|
||||
|
||||
public void registerDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.registerDeleteBean(hash);
|
||||
}
|
||||
|
||||
public void unregisterDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.unregisterDeleteBean(hash);
|
||||
}
|
||||
|
||||
public boolean isRegisteredForDeleteBean() {
|
||||
if (transaction == null){
|
||||
return false;
|
||||
} else {
|
||||
Integer hash = getBeanHash();
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return true if this bean has been already been persisted (inserted or updated) in this
|
||||
* transaction.
|
||||
*/
|
||||
public boolean isRegisteredBean() {
|
||||
return transaction.isRegisteredBean(bean);
|
||||
}
|
||||
|
||||
public BeanManager<T> getBeanManager() {
|
||||
return beanManager;
|
||||
}
|
||||
public void unRegisterBean() {
|
||||
transaction.unregisterBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for the associated bean.
|
||||
*/
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return beanDescriptor;
|
||||
}
|
||||
/**
|
||||
* The hash used to register the bean with the transaction.
|
||||
* <p>
|
||||
* Takes into account the class type and id value.
|
||||
* </p>
|
||||
*/
|
||||
private Integer getBeanHash() {
|
||||
if (beanHash == null) {
|
||||
Object id = beanDescriptor.getId(entityBean);
|
||||
int hc = 31 * bean.getClass().getName().hashCode();
|
||||
if (id != null) {
|
||||
hc += id.hashCode();
|
||||
}
|
||||
beanHash = Integer.valueOf(hc);
|
||||
}
|
||||
return beanHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a stateless update should also delete any missing details
|
||||
* beans.
|
||||
*/
|
||||
public boolean isDeleteMissingChildren() {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
public void registerDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.registerDeleteBean(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
|
||||
*/
|
||||
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
|
||||
this.deleteMissingChildren = deleteMissingChildren;
|
||||
}
|
||||
public void unregisterDeleteBean() {
|
||||
Integer hash = getBeanHash();
|
||||
transaction.unregisterDeleteBean(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to skip updates if we know the bean is not dirty. This is the case
|
||||
* for EntityBeans that have not been modified.
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
public boolean isRegisteredForDeleteBean() {
|
||||
if (transaction == null) {
|
||||
return false;
|
||||
} else {
|
||||
Integer hash = getBeanHash();
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the concurrency mode used for this persist.
|
||||
*/
|
||||
public ConcurrencyMode getConcurrencyMode() {
|
||||
return concurrencyMode;
|
||||
}
|
||||
public BeanManager<T> getBeanManager() {
|
||||
return beanManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the request. This is typically the bean class
|
||||
* name or the base table for MapBeans.
|
||||
* <p>
|
||||
* Used to determine common persist requests for queueing and statement
|
||||
* batching.
|
||||
* </p>
|
||||
*/
|
||||
public String getFullName() {
|
||||
return beanDescriptor.getFullName();
|
||||
}
|
||||
/**
|
||||
* Return the BeanDescriptor for the associated bean.
|
||||
*/
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return beanDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean associated with this request.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
/**
|
||||
* Return true if a stateless update should also delete any missing details beans.
|
||||
*/
|
||||
public boolean isDeleteMissingChildren() {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
|
||||
|
||||
public EntityBean getEntityBean() {
|
||||
/**
|
||||
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
|
||||
*/
|
||||
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
|
||||
this.deleteMissingChildren = deleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the update after potential modifications in a BeanPersistController.
|
||||
*/
|
||||
public void postControllerPrepareUpdate() {
|
||||
if (intercept.isNew() && controller != null) {
|
||||
// 'stateless update' - set dirty properties modified in controller preUpdate
|
||||
intercept.setNewBeanForUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to skip updates if we know the bean is not dirty. This is the case for EntityBeans that
|
||||
* have not been modified.
|
||||
*/
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the concurrency mode used for this persist.
|
||||
*/
|
||||
public ConcurrencyMode getConcurrencyMode() {
|
||||
return concurrencyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the request. This is typically the bean class name or the base table
|
||||
* for MapBeans.
|
||||
* <p>
|
||||
* Used to determine common persist requests for queueing and statement batching.
|
||||
* </p>
|
||||
*/
|
||||
public String getFullName() {
|
||||
return beanDescriptor.getFullName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean associated with this request.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
public EntityBean getEntityBean() {
|
||||
return entityBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Id value for the bean.
|
||||
*/
|
||||
public Object getBeanId() {
|
||||
return beanDescriptor.getId(entityBean);
|
||||
}
|
||||
* Return the Id value for the bean.
|
||||
*/
|
||||
public Object getBeanId() {
|
||||
return beanDescriptor.getId(entityBean);
|
||||
}
|
||||
|
||||
public BeanDelta createDeltaBean() {
|
||||
return new BeanDelta(beanDescriptor, getBeanId());
|
||||
}
|
||||
public BeanDelta createDeltaBean() {
|
||||
return new BeanDelta(beanDescriptor, getBeanId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent bean for cascading save with unidirectional
|
||||
* relationship.
|
||||
*/
|
||||
public Object getParentBean() {
|
||||
return parentBean;
|
||||
}
|
||||
/**
|
||||
* Return the parent bean for cascading save with unidirectional relationship.
|
||||
*/
|
||||
public Object getParentBean() {
|
||||
return parentBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the controller if there is one associated with this type of bean.
|
||||
* This returns null if there is no controller associated.
|
||||
*/
|
||||
public BeanPersistController getBeanController() {
|
||||
return controller;
|
||||
}
|
||||
/**
|
||||
* Return the controller if there is one associated with this type of bean. This returns null if
|
||||
* there is no controller associated.
|
||||
*/
|
||||
public BeanPersistController getBeanController() {
|
||||
return controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the intercept if there is one.
|
||||
*/
|
||||
public EntityBeanIntercept getEntityBeanIntercept() {
|
||||
return intercept;
|
||||
}
|
||||
/**
|
||||
* Return the intercept if there is one.
|
||||
*/
|
||||
public EntityBeanIntercept getEntityBeanIntercept() {
|
||||
return intercept;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is loaded (full bean or included in partial
|
||||
* bean).
|
||||
*/
|
||||
public boolean isLoadedProperty(BeanProperty prop) {
|
||||
return intercept.isLoadedProperty(prop.getPropertyIndex());
|
||||
}
|
||||
/**
|
||||
* Return true if this property is loaded (full bean or included in partial bean).
|
||||
*/
|
||||
public boolean isLoadedProperty(BeanProperty prop) {
|
||||
return intercept.isLoadedProperty(prop.getPropertyIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
persistExecute.executeInsertBean(this);
|
||||
return -1;
|
||||
@Override
|
||||
public int executeNow() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
persistExecute.executeInsertBean(this);
|
||||
return -1;
|
||||
|
||||
case UPDATE:
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
case UPDATE:
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
|
||||
case DELETE:
|
||||
persistExecute.executeDeleteBean(this);
|
||||
return -1;
|
||||
case DELETE:
|
||||
persistExecute.executeDeleteBean(this);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("Invalid type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
|
||||
boolean batch = transaction.isBatchThisRequest();
|
||||
boolean batch = transaction.isBatchThisRequest();
|
||||
|
||||
BatchControl control = transaction.getBatchControl();
|
||||
if (control != null) {
|
||||
return control.executeOrQueue(this, batch);
|
||||
}
|
||||
if (batch) {
|
||||
control = persistExecute.createBatchControl(transaction);
|
||||
return control.executeOrQueue(this, batch);
|
||||
BatchControl control = transaction.getBatchControl();
|
||||
if (control != null) {
|
||||
return control.executeOrQueue(this, batch);
|
||||
}
|
||||
if (batch) {
|
||||
control = persistExecute.createBatchControl(transaction);
|
||||
return control.executeOrQueue(this, batch);
|
||||
|
||||
} else {
|
||||
return executeNow();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return executeNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the generated key back to the bean. Only used for inserts with
|
||||
* getGeneratedKeys.
|
||||
*/
|
||||
public void setGeneratedKey(Object idValue) {
|
||||
if (idValue != null) {
|
||||
// remember it for logging summary
|
||||
this.idValue = beanDescriptor.convertSetId(idValue, entityBean);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the generated key back to the bean. Only used for inserts with getGeneratedKeys.
|
||||
*/
|
||||
public void setGeneratedKey(Object idValue) {
|
||||
if (idValue != null) {
|
||||
// remember it for logging summary
|
||||
this.idValue = beanDescriptor.convertSetId(idValue, entityBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Id value that was bound. Used for the purposes of logging summary
|
||||
* information on this request.
|
||||
*/
|
||||
public void setBoundId(Object idValue) {
|
||||
this.idValue = idValue;
|
||||
}
|
||||
/**
|
||||
* Set the Id value that was bound. Used for the purposes of logging summary information on this
|
||||
* request.
|
||||
*/
|
||||
public void setBoundId(Object idValue) {
|
||||
this.idValue = idValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for optimistic concurrency exception.
|
||||
*/
|
||||
public final void checkRowCount(int rowCount) throws SQLException {
|
||||
if (rowCount != 1) {
|
||||
String m = Message.msg("persist.conc2", "" + rowCount);
|
||||
throw new OptimisticLockException(m, null, bean);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check for optimistic concurrency exception.
|
||||
*/
|
||||
public final void checkRowCount(int rowCount) throws SQLException {
|
||||
if (rowCount != 1) {
|
||||
String m = Message.msg("persist.conc2", "" + rowCount);
|
||||
throw new OptimisticLockException(m, null, bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void postDelete() {
|
||||
|
||||
// Delete the bean from the PersistenceContent
|
||||
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
|
||||
// Delete from cache early even if transaction fails
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post processing.
|
||||
*/
|
||||
public void postExecute() throws SQLException {
|
||||
public void postDelete() {
|
||||
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
// Delete the bean from the PersistenceContent
|
||||
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
|
||||
// Delete from cache early even if transaction fails
|
||||
beanDescriptor.cacheHandleDelete(idValue, this);
|
||||
}
|
||||
|
||||
if (intercept != null) {
|
||||
// if bean persisted again then should result in an update
|
||||
intercept.setLoaded();
|
||||
}
|
||||
/**
|
||||
* Post processing.
|
||||
*/
|
||||
public void postExecute() throws SQLException {
|
||||
|
||||
addEvent();
|
||||
if (controller != null) {
|
||||
controllerPost();
|
||||
}
|
||||
|
||||
if (isLogSummary()) {
|
||||
logSummary();
|
||||
}
|
||||
}
|
||||
if (intercept != null) {
|
||||
// if bean persisted again then should result in an update
|
||||
intercept.setLoaded();
|
||||
}
|
||||
|
||||
private void controllerPost() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
controller.postInsert(this);
|
||||
break;
|
||||
case UPDATE:
|
||||
controller.postUpdate(this);
|
||||
break;
|
||||
case DELETE:
|
||||
controller.postDelete(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
addEvent();
|
||||
|
||||
private void logSummary() {
|
||||
if (isLogSummary()) {
|
||||
logSummary();
|
||||
}
|
||||
}
|
||||
|
||||
String name = beanDescriptor.getName();
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case UPDATE:
|
||||
transaction.logSummary("Updated [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case DELETE:
|
||||
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void controllerPost() {
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
controller.postInsert(this);
|
||||
break;
|
||||
case UPDATE:
|
||||
controller.postUpdate(this);
|
||||
break;
|
||||
case DELETE:
|
||||
controller.postDelete(this);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the TransactionEvent. This will be used by
|
||||
* TransactionManager to synch Cache, Cluster and text indexes.
|
||||
*/
|
||||
private void addEvent() {
|
||||
private void logSummary() {
|
||||
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
event.add(this);
|
||||
}
|
||||
}
|
||||
String name = beanDescriptor.getName();
|
||||
switch (type) {
|
||||
case INSERT:
|
||||
transaction.logSummary("Inserted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case UPDATE:
|
||||
transaction.logSummary("Updated [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
case DELETE:
|
||||
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the concurrency mode depending on fully/partially populated
|
||||
* bean.
|
||||
* <p>
|
||||
* Specifically with version concurrency we want to check that the version
|
||||
* property was one of the loaded properties.
|
||||
* </p>
|
||||
*/
|
||||
public ConcurrencyMode determineConcurrencyMode() {
|
||||
|
||||
// 'partial bean' update/delete...
|
||||
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 {
|
||||
concurrencyMode = ConcurrencyMode.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return concurrencyMode;
|
||||
}
|
||||
/**
|
||||
* Add the bean to the TransactionEvent. This will be used by TransactionManager to synch Cache,
|
||||
* Cluster and text indexes.
|
||||
*/
|
||||
private void addEvent() {
|
||||
|
||||
/**
|
||||
* Return true if the update DML/SQL must be dynamically generated.
|
||||
* <p>
|
||||
* This is the case for updates/deletes of partially populated beans.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDynamicUpdateSql() {
|
||||
return beanDescriptor.isUpdateChangesOnly() || !intercept.isFullyLoadedBean();
|
||||
}
|
||||
TransactionEvent event = transaction.getEvent();
|
||||
if (event != null) {
|
||||
event.add(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenerateDmlRequest used to generate the DML.
|
||||
* <p>
|
||||
* Will used changed properties or loaded properties depending on the
|
||||
* BeanDescriptor.isUpdateChangesOnly() value.
|
||||
* </p>
|
||||
*/
|
||||
public GenerateDmlRequest createGenerateDmlRequest(boolean emptyStringAsNull) {
|
||||
return new GenerateDmlRequest(emptyStringAsNull, intercept, beanDescriptor.isUpdateChangesOnly());
|
||||
}
|
||||
/**
|
||||
* Determine the concurrency mode depending on fully/partially populated bean.
|
||||
* <p>
|
||||
* Specifically with version concurrency we want to check that the version property was one of the
|
||||
* loaded properties.
|
||||
* </p>
|
||||
*/
|
||||
public ConcurrencyMode determineConcurrencyMode() {
|
||||
|
||||
/**
|
||||
* Test if the property value has changed and if so include it in the
|
||||
* update.
|
||||
*/
|
||||
public boolean isAddToUpdate(BeanProperty prop) {
|
||||
return intercept.isDirtyProperty(prop.getPropertyIndex());
|
||||
}
|
||||
// 'partial bean' update/delete...
|
||||
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 {
|
||||
concurrencyMode = ConcurrencyMode.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
return concurrencyMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the update DML/SQL must be dynamically generated.
|
||||
* <p>
|
||||
* This is the case for updates/deletes of partially populated beans.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isDynamicUpdateSql() {
|
||||
return beanDescriptor.isUpdateChangesOnly() || !intercept.isFullyLoadedBean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GenerateDmlRequest used to generate the DML.
|
||||
* <p>
|
||||
* Will used changed properties or loaded properties depending on the
|
||||
* BeanDescriptor.isUpdateChangesOnly() value.
|
||||
* </p>
|
||||
*/
|
||||
public GenerateDmlRequest createGenerateDmlRequest(boolean emptyStringAsNull) {
|
||||
return new GenerateDmlRequest(emptyStringAsNull, intercept, beanDescriptor.isUpdateChangesOnly());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the property value has changed and if so include it in the update.
|
||||
*/
|
||||
public boolean isAddToUpdate(BeanProperty prop) {
|
||||
return intercept.isDirtyProperty(prop.getPropertyIndex());
|
||||
}
|
||||
|
||||
public List<DerivedRelationshipData> getDerivedRelationships() {
|
||||
return transaction.getDerivedRelationship(bean);
|
||||
}
|
||||
|
||||
public void postInsert() {
|
||||
// mark all properties as loaded after an insert to support immediate update
|
||||
// mark all properties as loaded after an insert to support immediate update
|
||||
int len = intercept.getPropertyLength();
|
||||
for (int i = 0; i < len; i++) {
|
||||
intercept.setLoadedProperty(i);
|
||||
intercept.setLoadedProperty(i);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
return beanDescriptor.isReference(intercept);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This many property has been cascade saved. Keep note of this and update the 'many property'
|
||||
* cache on post commit.
|
||||
*/
|
||||
public void addUpdatedManyProperty(BeanPropertyAssocMany<?> updatedAssocMany) {
|
||||
//if (notifyCache) {
|
||||
if (updatedManys == null) {
|
||||
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
|
||||
}
|
||||
updatedManys.add(updatedAssocMany);
|
||||
//}
|
||||
// if (notifyCache) {
|
||||
if (updatedManys == null) {
|
||||
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
|
||||
}
|
||||
updatedManys.add(updatedAssocMany);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,10 @@ package com.avaje.ebeaninternal.server.ddl;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
@@ -12,8 +16,6 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
|
||||
import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
|
||||
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Used as part of CreateTableVisitor to generated the create table DDL script.
|
||||
@@ -71,7 +73,7 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
|
||||
private StringBuilder createUniqueConstraintBuffer(String table, String column) {
|
||||
|
||||
String uqConstraintName = "uq_" + table + "_" + column;
|
||||
String uqConstraintName = "uq_"+TableName.parse(table)+"_"+column;
|
||||
|
||||
if (uqConstraintName.length() > ddl.getMaxConstraintNameLength()) {
|
||||
uqConstraintName = uqConstraintName.substring(0, ddl.getMaxConstraintNameLength());
|
||||
@@ -153,11 +155,12 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
parent.writeColumnName(p.getDbColumn(), p);
|
||||
|
||||
String columnDefn = ctx.getColumnDefn(p);
|
||||
ctx.write(columnDefn);
|
||||
|
||||
|
||||
boolean identity = isIdentity(p);
|
||||
if (identity) {
|
||||
writeIdentity();
|
||||
ctx.write(ddl.getIdentityColumnDefn(columnDefn));
|
||||
} else {
|
||||
ctx.write(columnDefn);
|
||||
}
|
||||
|
||||
if (p.isId() && ddl.isInlinePrimaryKeyConstraint()){
|
||||
@@ -186,13 +189,6 @@ public class CreateTableColumnVisitor extends BaseTablePropertyVisitor {
|
||||
expr.append(p.getDbColumn()).append(")");
|
||||
return expr.toString();
|
||||
}
|
||||
|
||||
protected void writeIdentity() {
|
||||
String identity = ddl.getIdentity();
|
||||
if (identity != null && identity.length() > 0) {
|
||||
ctx.write(" ").write(identity);
|
||||
}
|
||||
}
|
||||
|
||||
protected void writeIdentitySuffix() {
|
||||
String identity = ddl.getIdentitySuffix();
|
||||
|
||||
@@ -4,6 +4,10 @@ import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.config.TableName;
|
||||
import com.avaje.ebean.config.dbplatform.DbDdlSyntax;
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
@@ -13,8 +17,6 @@ import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound;
|
||||
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
|
||||
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
|
||||
import com.avaje.ebeaninternal.server.deploy.parse.SqlReservedWords;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Used to generated the create table DDL script.
|
||||
@@ -23,23 +25,22 @@ public class CreateTableVisitor extends AbstractBeanVisitor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CreateTableVisitor.class);
|
||||
|
||||
final DdlGenContext ctx;
|
||||
private final DdlGenContext ctx;
|
||||
|
||||
final PropertyVisitor pv;
|
||||
private final PropertyVisitor pv;
|
||||
|
||||
final DbDdlSyntax ddl;
|
||||
private final DbDdlSyntax ddl;
|
||||
|
||||
final int columnNameWidth;
|
||||
private final int columnNameWidth;
|
||||
|
||||
// avoid writing columns twice, e.g. when used in associations with insertable=false and updateable=false
|
||||
private final Set<String> wroteColumns = new HashSet<String>();
|
||||
|
||||
private ArrayList<String> checkConstraints = new ArrayList<String>();
|
||||
private ArrayList<String> checkConstraints = new ArrayList<String>();
|
||||
|
||||
private ArrayList<String> uniqueConstraints = new ArrayList<String>();
|
||||
private ArrayList<String> uniqueConstraints = new ArrayList<String>();
|
||||
|
||||
private String table;
|
||||
private String schema;
|
||||
private String table;
|
||||
|
||||
public Set<String> getWroteColumns() {
|
||||
return wroteColumns;
|
||||
@@ -53,235 +54,211 @@ public class CreateTableVisitor extends AbstractBeanVisitor {
|
||||
this.pv = new CreateTableColumnVisitor(this, ctx);
|
||||
}
|
||||
|
||||
public boolean isDbColumnWritten(String dbColumn) {
|
||||
// Columns are not case sensitive - user lower case
|
||||
// as e.g. @JoinColumn(s) may use a different case
|
||||
return wroteColumns.contains(dbColumn.toLowerCase());
|
||||
}
|
||||
public boolean isDbColumnWritten(String dbColumn) {
|
||||
// Columns are not case sensitive - user lower case
|
||||
// as e.g. @JoinColumn(s) may use a different case
|
||||
return wroteColumns.contains(dbColumn.toLowerCase());
|
||||
}
|
||||
|
||||
public void addDbColumnWritten(String dbColumn){
|
||||
// Column names are case insensitive
|
||||
wroteColumns.add(dbColumn.toLowerCase());
|
||||
}
|
||||
public void addDbColumnWritten(String dbColumn) {
|
||||
// Column names are case insensitive
|
||||
wroteColumns.add(dbColumn.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the table name including a check for SQL reserved words.
|
||||
*/
|
||||
protected void writeTableName(BeanDescriptor<?> descriptor) {
|
||||
|
||||
String tableName = descriptor.getBaseTable();
|
||||
int dotPos = tableName.lastIndexOf('.');
|
||||
if (dotPos > -1){
|
||||
schema = tableName.substring(0, dotPos);
|
||||
table = tableName.substring(dotPos+1);
|
||||
} else {
|
||||
table = tableName;
|
||||
}
|
||||
|
||||
if (SqlReservedWords.isKeyword(table)) {
|
||||
logger.warn("Table name ["+table+"] is a suspected SQL reserved word for bean "+descriptor.getFullName());
|
||||
}
|
||||
|
||||
ctx.write(tableName);
|
||||
}
|
||||
|
||||
protected String getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
protected String getSchema() {
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the column name including a check for the SQL reserved words.
|
||||
* @param p
|
||||
*/
|
||||
protected void writeColumnName(String columnName, BeanProperty p) {
|
||||
|
||||
addDbColumnWritten(columnName);
|
||||
|
||||
if (SqlReservedWords.isKeyword(columnName)) {
|
||||
String propName = p == null ? "(Unknown)" : p.getFullBeanName();
|
||||
logger.warn("Column name ["+columnName+"] is a suspected SQL reserved word for property "+propName);
|
||||
}
|
||||
|
||||
ctx.write(" ").write(columnName, columnNameWidth).write(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a check constraint for the property if required.
|
||||
* <p>
|
||||
* Typically check constraint based on Enum mapping values.
|
||||
* </p>
|
||||
*/
|
||||
protected void addCheckConstraint(BeanProperty p, String prefix, String constraintExpression) {
|
||||
|
||||
if (p != null && constraintExpression != null){
|
||||
|
||||
// build constraint clause
|
||||
String s = "constraint "+getConstraintName(prefix, p)+" "+constraintExpression;
|
||||
|
||||
// add to list as we render all check constraints just prior to primary key
|
||||
checkConstraints.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getConstraintName(String prefix, BeanProperty p) {
|
||||
return prefix + table + "_" + p.getDbColumn();
|
||||
}
|
||||
|
||||
protected void addUniqueConstraint(String constraintExpression) {
|
||||
uniqueConstraints.add(constraintExpression);
|
||||
}
|
||||
/**
|
||||
* Write the table name including a check for SQL reserved words.
|
||||
*/
|
||||
protected void writeTableName(BeanDescriptor<?> descriptor) {
|
||||
|
||||
protected void addCheckConstraint(String constraintExpression) {
|
||||
checkConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(BeanProperty p) {
|
||||
addCheckConstraint(p,"ck_", p.getDbConstraintExpression());
|
||||
}
|
||||
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
wroteColumns.clear();
|
||||
|
||||
if (!descriptor.isInheritanceRoot()){
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.write("create table ");
|
||||
writeTableName(descriptor);
|
||||
ctx.write(" (").writeNewLine();
|
||||
|
||||
InheritInfo inheritInfo = descriptor.getInheritInfo();
|
||||
if (inheritInfo != null && inheritInfo.isRoot()){
|
||||
String discColumn = inheritInfo.getDiscriminatorColumn();
|
||||
int discType = inheritInfo.getDiscriminatorType();
|
||||
int discLength = inheritInfo.getDiscriminatorLength();
|
||||
DbType dbType = ctx.getDbTypeMap().get(discType);
|
||||
String discDbType = dbType.renderType(discLength, 0);
|
||||
|
||||
writeColumnName(discColumn, null);
|
||||
ctx.write(discDbType);
|
||||
ctx.write(" not null,");
|
||||
ctx.writeNewLine();
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
// parse to remove any catalog or schema prefix on the table
|
||||
table = TableName.parse(descriptor.getBaseTable());
|
||||
ctx.write(descriptor.getBaseTable());
|
||||
}
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
|
||||
if (checkConstraints.size() > 0){
|
||||
for (String checkConstraint : checkConstraints) {
|
||||
ctx.write(" ").write(checkConstraint).write(",").writeNewLine();
|
||||
}
|
||||
checkConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
if (uniqueConstraints.size() > 0){
|
||||
for (String constraint : uniqueConstraints) {
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
uniqueConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints();
|
||||
if (compoundUniqueConstraints != null){
|
||||
String table = descriptor.getBaseTable();
|
||||
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
|
||||
String constraint = createUniqueConstraint(table, i, compoundUniqueConstraints[i]);
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BeanProperty idProp = descriptor.getIdProperty();
|
||||
/**
|
||||
* Write the column name including a check for the SQL reserved words.
|
||||
*/
|
||||
protected void writeColumnName(String columnName, BeanProperty p) {
|
||||
|
||||
if (idProp == null){
|
||||
// No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
} else if (ddl.isInlinePrimaryKeyConstraint()) {
|
||||
// The Primary Key constraint was inlined with the column
|
||||
// ... No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
|
||||
} else {
|
||||
// Add the primay key constraint
|
||||
String pkName = ddl.getPrimaryKeyName(table);
|
||||
ctx.write(" constraint ").write(pkName).write(" primary key (");
|
||||
|
||||
VisitorUtil.visit(idProp, new AbstractPropertyVisitor() {
|
||||
|
||||
@Override
|
||||
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
});
|
||||
// remove the last comma, end of PK
|
||||
ctx.removeLast().write(")");
|
||||
}
|
||||
|
||||
// end of table
|
||||
ctx.write(")").writeNewLine();
|
||||
ctx.write(";").writeNewLine().writeNewLine();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private String createUniqueConstraint(String table, int idx, CompoundUniqueContraint uc) {
|
||||
|
||||
String uqConstraintName = "uq_"+table+"_"+(idx+1) ;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("constraint ")
|
||||
.append(uqConstraintName)
|
||||
.append(" unique (");
|
||||
|
||||
String[] columns = uc.getColumns();
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0){
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(columns[i]);
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
addDbColumnWritten(columnName);
|
||||
|
||||
if (SqlReservedWords.isKeyword(columnName)) {
|
||||
String propName = p == null ? "(Unknown)" : p.getFullBeanName();
|
||||
logger.warn("Column name [" + columnName + "] is a suspected SQL reserved word for property " + propName);
|
||||
}
|
||||
|
||||
ctx.write(" ").write(columnName, columnNameWidth).write(" ");
|
||||
}
|
||||
|
||||
public void visitBeanDescriptorEnd() {
|
||||
ctx.write(");").writeNewLine().writeNewLine();
|
||||
}
|
||||
/**
|
||||
* Build a check constraint for the property if required.
|
||||
* <p>
|
||||
* Typically check constraint based on Enum mapping values.
|
||||
* </p>
|
||||
*/
|
||||
protected void addCheckConstraint(BeanProperty p, String prefix, String constraintExpression) {
|
||||
|
||||
if (p != null && constraintExpression != null) {
|
||||
|
||||
public PropertyVisitor visitProperty(BeanProperty p) {
|
||||
return pv;
|
||||
}
|
||||
// build constraint clause
|
||||
String s = "constraint " + getConstraintName(prefix, p) + " " + constraintExpression;
|
||||
|
||||
public void visitBegin() {
|
||||
|
||||
}
|
||||
// add to list as we render all check constraints just prior to primary key
|
||||
checkConstraints.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
ctx.addIntersectionCreateTables();
|
||||
ctx.flush();
|
||||
}
|
||||
protected String getConstraintName(String prefix, BeanProperty p) {
|
||||
return prefix + table + "_" + p.getDbColumn();
|
||||
}
|
||||
|
||||
protected void addUniqueConstraint(String constraintExpression) {
|
||||
uniqueConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(String constraintExpression) {
|
||||
checkConstraints.add(constraintExpression);
|
||||
}
|
||||
|
||||
protected void addCheckConstraint(BeanProperty p) {
|
||||
addCheckConstraint(p, "ck_", p.getDbConstraintExpression());
|
||||
}
|
||||
|
||||
public boolean visitBean(BeanDescriptor<?> descriptor) {
|
||||
|
||||
wroteColumns.clear();
|
||||
|
||||
if (!descriptor.isInheritanceRoot()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx.write("create table ");
|
||||
writeTableName(descriptor);
|
||||
ctx.write(" (").writeNewLine();
|
||||
|
||||
InheritInfo inheritInfo = descriptor.getInheritInfo();
|
||||
if (inheritInfo != null && inheritInfo.isRoot()) {
|
||||
String discColumn = inheritInfo.getDiscriminatorColumn();
|
||||
int discType = inheritInfo.getDiscriminatorType();
|
||||
int discLength = inheritInfo.getDiscriminatorLength();
|
||||
DbType dbType = ctx.getDbTypeMap().get(discType);
|
||||
String discDbType = dbType.renderType(discLength, 0);
|
||||
|
||||
writeColumnName(discColumn, null);
|
||||
ctx.write(discDbType);
|
||||
ctx.write(" not null,");
|
||||
ctx.writeNewLine();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void visitBeanEnd(BeanDescriptor<?> descriptor) {
|
||||
|
||||
visitInheritanceProperties(descriptor, pv);
|
||||
|
||||
if (checkConstraints.size() > 0) {
|
||||
for (String checkConstraint : checkConstraints) {
|
||||
ctx.write(" ").write(checkConstraint).write(",").writeNewLine();
|
||||
}
|
||||
checkConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
if (uniqueConstraints.size() > 0) {
|
||||
for (String constraint : uniqueConstraints) {
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
uniqueConstraints = new ArrayList<String>();
|
||||
}
|
||||
|
||||
CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints();
|
||||
if (compoundUniqueConstraints != null) {
|
||||
String table = descriptor.getBaseTable();
|
||||
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
|
||||
String constraint = createUniqueConstraint(table, i, compoundUniqueConstraints[i]);
|
||||
ctx.write(" ").write(constraint).write(",").writeNewLine();
|
||||
}
|
||||
}
|
||||
|
||||
BeanProperty idProp = descriptor.getIdProperty();
|
||||
|
||||
if (idProp == null) {
|
||||
// No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
} else if (ddl.isInlinePrimaryKeyConstraint()) {
|
||||
// The Primary Key constraint was inlined with the column
|
||||
// ... No comma + new line
|
||||
ctx.removeLast().removeLast();
|
||||
|
||||
} else {
|
||||
// Add the primay key constraint
|
||||
String pkName = ddl.getPrimaryKeyName(table);
|
||||
ctx.write(" constraint ").write(pkName).write(" primary key (");
|
||||
|
||||
VisitorUtil.visit(idProp, new AbstractPropertyVisitor() {
|
||||
|
||||
@Override
|
||||
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitScalar(BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) {
|
||||
ctx.write(p.getDbColumn()).write(", ");
|
||||
}
|
||||
|
||||
});
|
||||
// remove the last comma, end of PK
|
||||
ctx.removeLast().write(")");
|
||||
}
|
||||
|
||||
// end of table
|
||||
ctx.write(")").writeNewLine();
|
||||
ctx.write(";").writeNewLine().writeNewLine();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private String createUniqueConstraint(String table, int idx, CompoundUniqueContraint uc) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
sb.append("constraint ")
|
||||
.append("uq_").append(TableName.parse(table)).append("_").append(idx + 1)
|
||||
.append(" unique (");
|
||||
|
||||
String[] columns = uc.getColumns();
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(columns[i]);
|
||||
}
|
||||
sb.append(")");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void visitBeanDescriptorEnd() {
|
||||
ctx.write(");").writeNewLine().writeNewLine();
|
||||
}
|
||||
|
||||
public PropertyVisitor visitProperty(BeanProperty p) {
|
||||
return pv;
|
||||
}
|
||||
|
||||
public void visitBegin() {
|
||||
|
||||
}
|
||||
|
||||
public void visitEnd() {
|
||||
ctx.addIntersectionCreateTables();
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -220,6 +220,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
*/
|
||||
private final BeanProperty[] propertiesLocal;
|
||||
|
||||
/**
|
||||
* Scalar mutable properties (need to dirty check on update).
|
||||
*/
|
||||
private final BeanProperty[] propertiesMutable;
|
||||
|
||||
|
||||
private final BeanPropertyAssocOne<?> unidirectional;
|
||||
|
||||
/**
|
||||
@@ -391,6 +397,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
this.propertiesBaseCompound = listHelper.getBaseCompound();
|
||||
this.propertiesEmbedded = listHelper.getEmbedded();
|
||||
this.propertiesLocal = listHelper.getLocal();
|
||||
this.propertiesMutable = listHelper.getMutable();
|
||||
this.unidirectional = listHelper.getUnidirectional();
|
||||
this.propertiesOne = listHelper.getOnes();
|
||||
this.propertiesOneExported = listHelper.getOneExported();
|
||||
@@ -1918,7 +1925,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for mutable scalar types and mark as dirty if necessary.
|
||||
*/
|
||||
public void checkMutableProperties(EntityBeanIntercept ebi) {
|
||||
for (int i = 0; i < propertiesMutable.length; i++) {
|
||||
BeanProperty beanProperty = propertiesMutable[i];
|
||||
if (ebi.isDirtyProperty(beanProperty.getPropertyIndex())) {
|
||||
// already marked as dirty
|
||||
} else if (ebi.isLoadedProperty(beanProperty.getPropertyIndex())) {
|
||||
Object value = beanProperty.getValue(ebi.getOwner());
|
||||
if (value == null || beanProperty.isDirtyValue(value)) {
|
||||
// mutable scalar value which is considered dirty so mark
|
||||
// it as such so that it is included in an update
|
||||
ebi.markPropertyAsChanged(beanProperty.getPropertyIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ConcurrencyMode getConcurrencyMode(EntityBeanIntercept ebi) {
|
||||
|
||||
if (!hasVersionProperty(ebi)) {
|
||||
return ConcurrencyMode.NONE;
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Model;
|
||||
import com.avaje.ebean.RawSql;
|
||||
import com.avaje.ebean.RawSqlBuilder;
|
||||
import com.avaje.ebean.annotation.ConcurrencyMode;
|
||||
@@ -1411,6 +1412,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
// we got to the top of the inheritance
|
||||
return;
|
||||
}
|
||||
if (Model.class.equals(superclass)) {
|
||||
// top of the inheritance. Not enhancing Model at this stage
|
||||
return;
|
||||
}
|
||||
if (!EntityBean.class.isAssignableFrom(superclass)) {
|
||||
throw new IllegalStateException("Super type "+superclass+" is not enhanced?");
|
||||
}
|
||||
|
||||
@@ -477,6 +477,16 @@ public class BeanProperty implements ElPropertyValue {
|
||||
public boolean isDiscriminator() {
|
||||
return discriminator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the underlying type is mutable.
|
||||
*/
|
||||
public boolean isMutableScalarType() {
|
||||
if (scalarType == null) {
|
||||
return false;
|
||||
}
|
||||
return scalarType.isMutable();
|
||||
}
|
||||
|
||||
public void copyProperty(EntityBean sourceBean, EntityBean destBean) {
|
||||
Object value = getValue(sourceBean);
|
||||
@@ -868,6 +878,14 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return descriptor.getFullName() + "." + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the mutable value is considered dirty.
|
||||
* This is only used for 'mutable' scalar types like hstore etc.
|
||||
*/
|
||||
public boolean isDirtyValue(Object value) {
|
||||
return scalarType.isDirty(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the scalarType.
|
||||
*/
|
||||
|
||||
@@ -60,8 +60,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
*/
|
||||
private final boolean manyToMany;
|
||||
|
||||
/**
|
||||
* Order by used when fetch joining the associated many.
|
||||
*/
|
||||
private final String fetchOrderBy;
|
||||
|
||||
/**
|
||||
* Order by used when lazy loading the associated many.
|
||||
*/
|
||||
private String lazyFetchOrderBy;
|
||||
|
||||
private final String mapKey;
|
||||
|
||||
/**
|
||||
@@ -137,6 +145,22 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
if (exportedProperties.length > 0){
|
||||
embeddedExportedProperties = exportedProperties[0].isEmbedded();
|
||||
exportedPropertyBindProto = deriveExportedPropertyBindProto();
|
||||
|
||||
if (fetchOrderBy != null) {
|
||||
// derive lazyFetchOrderBy
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
for (int i = 0; i < exportedProperties.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
// these fkcolumns always on base table hence t0 as alias
|
||||
sb.append("t0.").append(exportedProperties[i].getForeignDbColumn());
|
||||
}
|
||||
if (fetchOrderBy != null) {
|
||||
sb.append(", ").append(fetchOrderBy);
|
||||
}
|
||||
lazyFetchOrderBy = sb.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
String delStmt;
|
||||
@@ -510,6 +534,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order by for use when lazy loading the associated collection.
|
||||
*/
|
||||
public String getLazyFetchOrderBy() {
|
||||
return lazyFetchOrderBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default mapKey when returning a Map.
|
||||
*/
|
||||
public String getMapKey() {
|
||||
|
||||
+11
-1
@@ -37,6 +37,8 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
private final ArrayList<BeanProperty> local = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> mutable = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> manys = new ArrayList<BeanProperty>();
|
||||
|
||||
private final ArrayList<BeanProperty> nonManys = new ArrayList<BeanProperty>();
|
||||
@@ -135,6 +137,10 @@ public class DeployBeanPropertyLists {
|
||||
nonTransients.add(prop);
|
||||
}
|
||||
|
||||
if (prop.isMutableScalarType()) {
|
||||
mutable.add(prop);
|
||||
}
|
||||
|
||||
if (desc.getInheritInfo() != null && prop.isLocal()) {
|
||||
local.add(prop);
|
||||
}
|
||||
@@ -197,7 +203,7 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
public BeanProperty getId() {
|
||||
if (ids.size() > 1) {
|
||||
String msg = "Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId."
|
||||
String msg = "Issue with bean "+desc+". Ebean does not support multiple @Id properties. You need to convert to using an @EmbeddedId."
|
||||
+" Please email the ebean google group if you need further clarification.";
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
@@ -223,6 +229,10 @@ public class DeployBeanPropertyLists {
|
||||
return (BeanProperty[]) local.toArray(new BeanProperty[local.size()]);
|
||||
}
|
||||
|
||||
public BeanProperty[] getMutable() {
|
||||
return (BeanProperty[]) mutable.toArray(new BeanProperty[mutable.size()]);
|
||||
}
|
||||
|
||||
public BeanPropertyAssocOne<?>[] getEmbedded() {
|
||||
return (BeanPropertyAssocOne[]) embedded.toArray(new BeanPropertyAssocOne[embedded.size()]);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Iterator;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
import com.avaje.ebean.annotation.ColumnHstore;
|
||||
import com.avaje.ebeaninternal.server.core.Message;
|
||||
import com.avaje.ebeaninternal.server.deploy.DetermineManyType;
|
||||
import com.avaje.ebeaninternal.server.deploy.ManyType;
|
||||
@@ -21,8 +22,10 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
|
||||
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
|
||||
import com.avaje.ebeaninternal.server.type.CtCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypePostgresHstore;
|
||||
import com.avaje.ebeaninternal.server.type.TypeManager;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -276,6 +279,16 @@ public class DeployCreateProperties {
|
||||
Class<?> propertyType = field.getType();
|
||||
Class<?> innerType = propertyType;
|
||||
|
||||
String specialTypeKey = getSpecialScalarType(field);
|
||||
if (specialTypeKey != null) {
|
||||
ScalarType<?> scalarType = typeManager.getScalarTypeFromKey(specialTypeKey);
|
||||
if (scalarType == null) {
|
||||
logger.error("Could not find ScalarType to match key ["+specialTypeKey+"]");
|
||||
} else {
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
}
|
||||
|
||||
// check for Collection type (list, set or map)
|
||||
ManyType manyType = determineManyType.getManyType(propertyType);
|
||||
|
||||
@@ -336,6 +349,15 @@ public class DeployCreateProperties {
|
||||
}
|
||||
}
|
||||
|
||||
private String getSpecialScalarType(Field field) {
|
||||
|
||||
if (field.getAnnotation(ColumnHstore.class) != null) {
|
||||
return ScalarTypePostgresHstore.KEY;
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isTransientField(Field field) {
|
||||
|
||||
Transient t = field.getAnnotation(Transient.class);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebeaninternal.server.lib.util;
|
||||
|
||||
/**
|
||||
* String utility for adding strings together.
|
||||
* <p>
|
||||
* Predicts a decent buffer size to append the strings into.
|
||||
*/
|
||||
public class Str {
|
||||
|
||||
/**
|
||||
* Append strings together.
|
||||
*/
|
||||
public static String add(String s0, String s1, String ... args) {
|
||||
|
||||
// determine a decent buffer size
|
||||
int len = 16 + s0.length() + s1.length();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
len += args[i].length();
|
||||
}
|
||||
|
||||
// append all the strings into the buffer
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
sb.append(s0).append(s1);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
sb.append(args[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append two strings together.
|
||||
*/
|
||||
public static String add(String s0, String s1) {
|
||||
StringBuilder sb = new StringBuilder(s0.length() + s1.length() + 5);
|
||||
return sb.append(s0).append(s1).toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -56,8 +56,9 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
if (currentBuffer.isFull()) {
|
||||
currentBuffer = createBuffer(secondaryBatchSize);
|
||||
}
|
||||
currentBuffer.add(ebi);
|
||||
// set the persistenceContext on the bean first
|
||||
ebi.setBeanLoader(0, currentBuffer, getPersistenceContext());
|
||||
currentBuffer.add(ebi);
|
||||
}
|
||||
|
||||
private LoadBuffer createBuffer(int size) {
|
||||
@@ -100,16 +101,13 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
*/
|
||||
public static class LoadBuffer implements BeanLoader, LoadBeanBuffer {
|
||||
|
||||
private final PersistenceContext persistenceContext;
|
||||
private final DLoadBeanContext context;
|
||||
private final int batchSize;
|
||||
private final List<EntityBeanIntercept> list;
|
||||
private PersistenceContext persistenceContext;
|
||||
|
||||
public LoadBuffer(DLoadBeanContext context, int batchSize) {
|
||||
this.context = context;
|
||||
// set the persistence context as at this moment in
|
||||
// case it changes as part of a findIterate etc
|
||||
this.persistenceContext = context.getPersistenceContext();
|
||||
this.batchSize = batchSize;
|
||||
this.list = new ArrayList<EntityBeanIntercept>(batchSize);
|
||||
}
|
||||
@@ -125,6 +123,10 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
* Return true if the buffer is full.
|
||||
*/
|
||||
public void add(EntityBeanIntercept ebi) {
|
||||
if (persistenceContext == null) {
|
||||
// get persistenceContext from first loaded bean into the buffer
|
||||
persistenceContext = ebi.getPersistenceContext();
|
||||
}
|
||||
list.add(ebi);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,6 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preInsert(request)) {
|
||||
persister.insert(request);
|
||||
// NOTE: the persister fires the postInsert so that this
|
||||
// occurs before ebeanIntercept.setLoaded(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +77,8 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preUpdate(request)) {
|
||||
request.postControllerPrepareUpdate();
|
||||
persister.update(request);
|
||||
// NOTE: the persister fires the postUpdate so that this
|
||||
// occurs before ebeanIntercept.setLoaded(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,9 +92,7 @@ public final class DefaultPersistExecute implements PersistExecute {
|
||||
|
||||
BeanPersistController controller = request.getBeanController();
|
||||
if (controller == null || controller.preDelete(request)) {
|
||||
|
||||
persister.delete(request);
|
||||
// NOTE: the persister fires the postDelete
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,14 +413,14 @@ public final class DefaultPersister implements Persister {
|
||||
if (t.isPersistCascade()) {
|
||||
// OneToOne exported side with delete cascade
|
||||
BeanPropertyAssocOne<?>[] expOnes = descriptor.propertiesOneExportedDelete();
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
BeanDescriptor<?> targetDesc = expOnes[i].getTargetDescriptor();
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
|
||||
delete(targetDesc, null, childIds, t);
|
||||
deleteChildrenById(t, targetDesc, childIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,12 +1057,33 @@ public final class DefaultPersister implements Persister {
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
|
||||
if (!idsByParentId.isEmpty()) {
|
||||
delete(targetDesc, null, idsByParentId, t);
|
||||
deleteChildrenById(t, targetDesc, idsByParentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cascade delete child entities by Id.
|
||||
* <p>
|
||||
* Will use delete by object if the child entity has manyToMany relationships.
|
||||
*/
|
||||
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds) {
|
||||
|
||||
if (targetDesc.propertiesManyToMany().length > 0) {
|
||||
// convert into a list of reference objects and perform delete by object
|
||||
List<Object> refList = new ArrayList<Object>(childIds.size());
|
||||
for (Object id : childIds) {
|
||||
refList.add(targetDesc.createReference(null, id));
|
||||
}
|
||||
deleteList(refList, t);
|
||||
|
||||
} else {
|
||||
// perform delete by statement if possible
|
||||
delete(targetDesc, null, childIds, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save any associated one beans.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,6 @@ import javax.persistence.OptimisticLockException;
|
||||
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
|
||||
import com.avaje.ebeaninternal.server.core.PstmtBatch;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchedPstmt;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchedPstmtHolder;
|
||||
import com.avaje.ebeaninternal.server.persist.dmlbind.BindableRequest;
|
||||
@@ -56,7 +57,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
this.transaction = persistRequest.getTransaction();
|
||||
this.logLevelSql = transaction.isLogSql();
|
||||
if (logLevelSql) {
|
||||
this.bindLog = new StringBuilder();
|
||||
this.bindLog = new StringBuilder(50);
|
||||
} else {
|
||||
this.bindLog = null;
|
||||
}
|
||||
@@ -137,7 +138,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
protected void logSql(String sql) {
|
||||
if (logLevelSql) {
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
sql += "; --bind(" + bindLog + ")";
|
||||
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
|
||||
}
|
||||
transaction.logSql(sql);
|
||||
}
|
||||
@@ -237,8 +238,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
/**
|
||||
* Check with useGeneratedKeys to get appropriate PreparedStatement.
|
||||
*/
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys)
|
||||
throws SQLException {
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean genKeys) throws SQLException {
|
||||
|
||||
Connection conn = t.getInternalConnection();
|
||||
if (genKeys) {
|
||||
@@ -266,10 +266,6 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
|
||||
return stmt;
|
||||
}
|
||||
|
||||
if (logLevelSql) {
|
||||
t.logSql(sql);
|
||||
}
|
||||
|
||||
stmt = getPstmt(t, sql, genKeys);
|
||||
|
||||
PstmtBatch pstmtBatch = request.getPstmtBatch();
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
@@ -115,7 +114,7 @@ public class InsertHandler extends DmlHandler {
|
||||
protected PreparedStatement getPstmt(SpiTransaction t, String sql, boolean useGeneratedKeys) throws SQLException {
|
||||
Connection conn = t.getInternalConnection();
|
||||
if (useGeneratedKeys) {
|
||||
return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
|
||||
return conn.prepareStatement(sql, meta.getIdentityDbColumns());
|
||||
|
||||
} else {
|
||||
return conn.prepareStatement(sql);
|
||||
|
||||
@@ -357,6 +357,16 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
SpiTransaction t = request.getTransaction();
|
||||
Connection conn = t.getInternalConnection();
|
||||
|
||||
if (query.isRawSql()) {
|
||||
ResultSet suppliedResultSet = query.getRawSql().getResultSet();
|
||||
if (suppliedResultSet != null) {
|
||||
// this is a user supplied ResultSet so use that
|
||||
dataReader = queryPlan.createDataReader(suppliedResultSet);
|
||||
bindLog = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (forwardOnlyHint) {
|
||||
// Use forward only hints for large resultset processing (Issue 56, MySql specific)
|
||||
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
||||
@@ -476,15 +486,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasMoreRows() throws SQLException {
|
||||
synchronized (this) {
|
||||
if (cancelled) {
|
||||
return false;
|
||||
}
|
||||
return dataReader.next();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a row from the result set returning a bean.
|
||||
* <p>
|
||||
@@ -533,7 +534,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
private boolean readBeanInternal() throws SQLException {
|
||||
|
||||
if (loadedBeanCount >= maxRowsLimit) {
|
||||
collection.setHasMoreRows(hasMoreRows());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,10 @@ public class CQueryBuilder implements Constants {
|
||||
|
||||
// always set the order by to null for row count query
|
||||
query.setOrder(null);
|
||||
if (query.isRawSql()) {
|
||||
query.setFirstRow(0);
|
||||
query.setMaxRows(0);
|
||||
}
|
||||
|
||||
ManyWhereJoins manyWhereJoins = query.getManyWhereJoins();
|
||||
|
||||
@@ -158,7 +162,7 @@ public class CQueryBuilder implements Constants {
|
||||
SqlTree sqlTree = createSqlTree(request, predicates);
|
||||
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
|
||||
String sql = s.getSql();
|
||||
if (hasMany) {
|
||||
if (hasMany || query.isRawSql()) {
|
||||
sql = "select count(*) from ( " + sql + ")";
|
||||
if (selectCountWithAlias) {
|
||||
sql += " as c";
|
||||
|
||||
@@ -25,7 +25,12 @@ public class CQueryBuilderRawSql implements Constants {
|
||||
* Build the full SQL Select statement for the request.
|
||||
*/
|
||||
public SqlLimitResponse buildSql(OrmQueryRequest<?> request, CQueryPredicates predicates, RawSql.Sql rsql) {
|
||||
|
||||
|
||||
if (rsql == null) {
|
||||
// this is a ResultSet based RawSql query - just use some placeholder for the SQL
|
||||
return new SqlLimitResponse("--ResultSetBasedRawSql", false);
|
||||
}
|
||||
|
||||
if (!rsql.isParsed()){
|
||||
String sql = rsql.getUnparsedSql();
|
||||
BindParams bindParams = request.getQuery().getBindParams();
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebeaninternal.api.BeanIdList;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.Binder;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
|
||||
|
||||
@@ -53,7 +54,7 @@ public class CQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = rcQuery.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+rcQuery.getBindLog()+")";
|
||||
logSql = Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")");
|
||||
}
|
||||
request.logSql(logSql);
|
||||
}
|
||||
@@ -88,7 +89,7 @@ public class CQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = rcQuery.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+rcQuery.getBindLog()+")";
|
||||
logSql= Str.add(logSql, "; --bind(", rcQuery.getBindLog(), ")");
|
||||
}
|
||||
request.logSql(logSql);
|
||||
}
|
||||
@@ -241,7 +242,7 @@ public class CQueryEngine {
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
sql += "; --bind("+query.getBindLog()+")";
|
||||
sql= Str.add(sql, "; --bind(", query.getBindLog(), ")");
|
||||
}
|
||||
query.getTransaction().logSql(sql);
|
||||
}
|
||||
|
||||
@@ -19,112 +19,107 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
*/
|
||||
public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
|
||||
/**
|
||||
* Find using predicates
|
||||
*/
|
||||
private final CQueryEngine queryEngine;
|
||||
|
||||
/**
|
||||
* Create the Finder.
|
||||
*/
|
||||
public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) {
|
||||
|
||||
this.queryEngine = queryEngine;
|
||||
/**
|
||||
* Find using predicates
|
||||
*/
|
||||
private final CQueryEngine queryEngine;
|
||||
|
||||
/**
|
||||
* Create the Finder.
|
||||
*/
|
||||
public DefaultOrmQueryEngine(BeanDescriptorManager descMgr, CQueryEngine queryEngine) {
|
||||
|
||||
this.queryEngine = queryEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
|
||||
*/
|
||||
private <T> void flushJdbcBatchOnQuery(OrmQueryRequest<T> request) {
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
if (t.isBatchFlushOnQuery()) {
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
}
|
||||
}
|
||||
|
||||
public <T> int findRowCount(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findRowCount(request);
|
||||
}
|
||||
|
||||
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findIds(request);
|
||||
}
|
||||
|
||||
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
|
||||
|
||||
// LIMITATION: You can not use QueryIterator to load bean cache
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
return queryEngine.findIterate(request);
|
||||
}
|
||||
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
|
||||
|
||||
flushJdbcBatchOnQuery(request);
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
|
||||
BeanCollection<T> result;
|
||||
if (finder != null) {
|
||||
// this bean type has its own specific finder
|
||||
result = finder.findMany(request);
|
||||
} else {
|
||||
result = queryEngine.findMany(request);
|
||||
}
|
||||
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
public <T> int findRowCount(OrmQueryRequest<T> request){
|
||||
|
||||
return queryEngine.findRowCount(request);
|
||||
if (query.isLoadBeanCache()) {
|
||||
// load the individual beans into the bean cache
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
for (T bean : c) {
|
||||
descriptor.cacheBeanPutData((EntityBean) bean);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> BeanIdList findIds(OrmQueryRequest<T> request){
|
||||
|
||||
return queryEngine.findIds(request);
|
||||
if (!result.isEmpty() && query.isUseQueryCache()) {
|
||||
// load the query result into the query cache
|
||||
request.putToQueryCache(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
|
||||
/**
|
||||
* Find a single bean using its unique id.
|
||||
*/
|
||||
public <T> T findId(OrmQueryRequest<T> request) {
|
||||
|
||||
// LIMITATION: You can not use QueryIterator to load bean cache
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
|
||||
return queryEngine.findIterate(request);
|
||||
}
|
||||
|
||||
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
|
||||
flushJdbcBatchOnQuery(request);
|
||||
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
BeanCollection<T> result = null;
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
if (finder != null) {
|
||||
// this bean type has its own specific finder
|
||||
result = finder.findMany(request);
|
||||
} else {
|
||||
result = queryEngine.findMany(request);
|
||||
}
|
||||
|
||||
if (query.isLoadBeanCache()){
|
||||
// load the individual beans into the bean cache
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
for (T bean : c) {
|
||||
descriptor.cacheBeanPutData((EntityBean)bean);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isEmpty() && query.isUseQueryCache()){
|
||||
// load the query result into the query cache
|
||||
request.putToQueryCache(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
T result;
|
||||
if (finder != null) {
|
||||
result = finder.find(request);
|
||||
} else {
|
||||
result = queryEngine.find(request);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a single bean using its unique id.
|
||||
*/
|
||||
public <T> T findId(OrmQueryRequest<T> request) {
|
||||
|
||||
T result = null;
|
||||
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
if (t.isBatchFlushOnQuery()){
|
||||
// before we perform a query, we need to flush any
|
||||
// previous persist requests that are queued/batched.
|
||||
// The query may read data affected by those requests.
|
||||
t.flushBatch();
|
||||
}
|
||||
|
||||
BeanFinder<T> finder = request.getBeanFinder();
|
||||
if (finder != null) {
|
||||
result = finder.find(request);
|
||||
} else {
|
||||
result = queryEngine.find(request);
|
||||
}
|
||||
|
||||
if (result != null && request.isUseBeanCache()){
|
||||
request.getBeanDescriptor().cacheBeanPutData((EntityBean)result);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (result != null && request.isUseBeanCache()) {
|
||||
request.getBeanDescriptor().cacheBeanPutData((EntityBean) result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-11
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.core.Message;
|
||||
import com.avaje.ebeaninternal.server.core.RelationalQueryEngine;
|
||||
import com.avaje.ebeaninternal.server.core.RelationalQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.Binder;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
@@ -94,7 +95,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
if (request.isLogSql()) {
|
||||
String logSql = sql;
|
||||
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
|
||||
logSql += "; --bind("+bindLog+")";
|
||||
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
|
||||
}
|
||||
t.logSql(logSql);
|
||||
}
|
||||
@@ -115,8 +116,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
maxRows = query.getMaxRows();
|
||||
}
|
||||
|
||||
boolean hasHitMaxRows = false;
|
||||
|
||||
int loadRowCount = 0;
|
||||
|
||||
SqlQueryListener listener = query.getListener();
|
||||
@@ -152,7 +151,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
|
||||
if (loadRowCount == maxRows) {
|
||||
// break, as we have hit the max rows to fetch...
|
||||
hasHitMaxRows = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -160,13 +158,6 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
|
||||
|
||||
BeanCollection<?> beanColl = wrapper.getBeanCollection();
|
||||
|
||||
if (hasHitMaxRows) {
|
||||
if (rset.next()) {
|
||||
// there are more rows available after the maxRows limit
|
||||
beanColl.setHasMoreRows(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.isLogSummary()) {
|
||||
long exeTime = System.currentTimeMillis() - startTime;
|
||||
String msg = "SqlQuery rows[" + loadRowCount + "] time[" + exeTime + "] bind[" + bindLog + "]";
|
||||
|
||||
@@ -52,7 +52,7 @@ public class LimitOffsetPage<T> implements Page<T>, BeanCollectionTouched {
|
||||
* Perform fetch ahead when the list is first accessed.
|
||||
*/
|
||||
public void notifyTouched(BeanCollection<?> c) {
|
||||
if (c.hasMoreRows()) {
|
||||
if (hasNext()) {
|
||||
owner.fetchAheadIfRequired(pageIndex);
|
||||
}
|
||||
}
|
||||
@@ -65,9 +65,8 @@ public class LimitOffsetPage<T> implements Page<T>, BeanCollectionTouched {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasNext() {
|
||||
return ((BeanCollection<T>) getList()).hasMoreRows();
|
||||
return pageIndex < getTotalPageCount() - 1;
|
||||
}
|
||||
|
||||
public boolean hasPrev() {
|
||||
|
||||
@@ -6,12 +6,13 @@ import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
|
||||
public class OrmQueryLimitRequest implements SqlLimitRequest {
|
||||
|
||||
final SpiQuery<?> ormQuery;
|
||||
final DatabasePlatform dbPlatform;
|
||||
|
||||
final String sql;
|
||||
private final SpiQuery<?> ormQuery;
|
||||
|
||||
final String sqlOrderBy;
|
||||
private final DatabasePlatform dbPlatform;
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final String sqlOrderBy;
|
||||
|
||||
public OrmQueryLimitRequest(String sql, String sqlOrderBy, SpiQuery<?> ormQuery, DatabasePlatform dbPlatform) {
|
||||
this.sql = sql;
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.api.TransactionEvent;
|
||||
import com.avaje.ebeaninternal.server.lib.util.Str;
|
||||
import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager.OnQueryOnly;
|
||||
|
||||
@@ -37,92 +38,96 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
/**
|
||||
* The associated TransactionManager.
|
||||
*/
|
||||
final protected TransactionManager manager;
|
||||
protected final TransactionManager manager;
|
||||
|
||||
/**
|
||||
* The transaction id.
|
||||
*/
|
||||
final String id;
|
||||
protected final String id;
|
||||
|
||||
/**
|
||||
* Flag to indicate if this was an explicitly created Transaction.
|
||||
*/
|
||||
final boolean explicit;
|
||||
protected final boolean explicit;
|
||||
|
||||
/**
|
||||
* Set to true if the connection has autoCommit=true initially.
|
||||
*/
|
||||
final boolean autoCommit;
|
||||
protected final boolean autoCommit;
|
||||
|
||||
/**
|
||||
* Behaviour for ending query only transactions.
|
||||
*/
|
||||
final OnQueryOnly onQueryOnly;
|
||||
protected final OnQueryOnly onQueryOnly;
|
||||
|
||||
/**
|
||||
* The status of the transaction.
|
||||
*/
|
||||
boolean active;
|
||||
protected boolean active;
|
||||
|
||||
/**
|
||||
* The underlying Connection.
|
||||
*/
|
||||
Connection connection;
|
||||
protected Connection connection;
|
||||
|
||||
/**
|
||||
* Used to queue up persist requests for batch execution.
|
||||
*/
|
||||
BatchControl batchControl;
|
||||
protected BatchControl batchControl;
|
||||
|
||||
/**
|
||||
* The event which holds persisted beans.
|
||||
*/
|
||||
TransactionEvent event;
|
||||
protected TransactionEvent event;
|
||||
|
||||
/**
|
||||
* Holder of the objects fetched to ensure unique objects are used.
|
||||
*/
|
||||
PersistenceContext persistenceContext;
|
||||
protected PersistenceContext persistenceContext;
|
||||
|
||||
/**
|
||||
* Used to give developers more control over the insert update and delete
|
||||
* functionality.
|
||||
*/
|
||||
boolean persistCascade = true;
|
||||
protected boolean persistCascade = true;
|
||||
|
||||
/**
|
||||
* Flag used for performance to skip commit or rollback of query only
|
||||
* transactions in read committed transaction isolation.
|
||||
*/
|
||||
boolean queryOnly = true;
|
||||
protected boolean queryOnly = true;
|
||||
|
||||
boolean localReadOnly;
|
||||
protected boolean localReadOnly;
|
||||
|
||||
/**
|
||||
* Set to true if using batch processing.
|
||||
*/
|
||||
boolean batchMode;
|
||||
protected boolean batchMode;
|
||||
|
||||
int batchSize = -1;
|
||||
protected int batchSize = -1;
|
||||
|
||||
boolean batchFlushOnQuery = true;
|
||||
protected boolean batchFlushOnQuery = true;
|
||||
|
||||
Boolean batchGetGeneratedKeys;
|
||||
protected Boolean batchGetGeneratedKeys;
|
||||
|
||||
Boolean batchFlushOnMixed;
|
||||
protected Boolean batchFlushOnMixed;
|
||||
|
||||
String logPrefix;
|
||||
protected String logPrefix;
|
||||
|
||||
/**
|
||||
* The depth used by batch processing to help the ordering of statements.
|
||||
*/
|
||||
int depth = 0;
|
||||
protected int depth;
|
||||
|
||||
IdentityHashMap<Object,Object> persistingBeans;
|
||||
HashSet<Integer> deletingBeansHash;
|
||||
HashMap<String,String> m2mIntersectionSave;
|
||||
HashMap<Integer, List<DerivedRelationshipData>> derivedRelMap;
|
||||
Map<String, Object> userObjects;
|
||||
protected IdentityHashMap<Object,Object> persistingBeans;
|
||||
|
||||
protected HashSet<Integer> deletingBeansHash;
|
||||
|
||||
protected HashMap<String,String> m2mIntersectionSave;
|
||||
|
||||
protected HashMap<Integer, List<DerivedRelationshipData>> derivedRelMap;
|
||||
|
||||
protected Map<String, Object> userObjects;
|
||||
|
||||
/**
|
||||
* Create a new JdbcTransaction.
|
||||
@@ -472,13 +477,13 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
}
|
||||
|
||||
public void logSql(String msg) {
|
||||
TransactionManager.SQL_LOGGER.trace(logPrefix+msg);
|
||||
TransactionManager.SQL_LOGGER.trace(Str.add(logPrefix, msg));
|
||||
}
|
||||
|
||||
public void logSummary(String msg) {
|
||||
TransactionManager.SUM_LOGGER.debug(logPrefix+msg);
|
||||
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the transaction id.
|
||||
*/
|
||||
|
||||
@@ -45,6 +45,7 @@ import com.avaje.ebeaninternal.server.type.reflect.KnownImmutable;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundType;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundTypeProperty;
|
||||
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedTypeBuilder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -64,6 +65,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
|
||||
private final ConcurrentHashMap<Integer, ScalarType<?>> nativeMap;
|
||||
|
||||
private final ConcurrentHashMap<String, ScalarType<?>> customTypeMap;
|
||||
|
||||
private final DefaultTypeFactory extraTypeFactory;
|
||||
|
||||
private final ScalarType<?> charType = new ScalarTypeChar();
|
||||
@@ -136,6 +139,9 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
this.compoundTypeMap = new ConcurrentHashMap<Class<?>, CtCompoundType<?>>();
|
||||
this.typeMap = new ConcurrentHashMap<Class<?>, ScalarType<?>>();
|
||||
this.nativeMap = new ConcurrentHashMap<Integer, ScalarType<?>>();
|
||||
this.customTypeMap = new ConcurrentHashMap<String, ScalarType<?>>();
|
||||
|
||||
this.customTypeMap.put(ScalarTypePostgresHstore.KEY, new ScalarTypePostgresHstore());
|
||||
|
||||
this.extraTypeFactory = new DefaultTypeFactory(config);
|
||||
|
||||
@@ -149,6 +155,14 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a special or custom scalar type by key.
|
||||
*/
|
||||
@Override
|
||||
public ScalarType<?> getScalarTypeFromKey(String specialTypeKey) {
|
||||
return customTypeMap.get(specialTypeKey);
|
||||
}
|
||||
|
||||
public boolean isKnownImmutable(Class<?> cls) {
|
||||
|
||||
if (cls == null) {
|
||||
@@ -613,7 +627,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
* plus some other common types such as java.util.Date and java.util.Calendar.
|
||||
*/
|
||||
protected void initialiseStandard(int platformClobType, int platformBlobType, boolean binaryUUID) {
|
||||
|
||||
|
||||
ScalarType<?> utilDateType = extraTypeFactory.createUtilDate();
|
||||
typeMap.put(java.util.Date.class, utilDateType);
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Wraps a collection for the purposes of detecting modifications.
|
||||
*/
|
||||
public class ModifyAwareCollection<E> implements Collection<E> {
|
||||
|
||||
protected final ModifyAwareOwner owner;
|
||||
|
||||
protected final Collection<E> c;
|
||||
|
||||
/**
|
||||
* Create with an Owner and the underlying collection this wraps.
|
||||
* <p>
|
||||
* The owner is notified of the additions and removals.
|
||||
* </p>
|
||||
*/
|
||||
public ModifyAwareCollection(ModifyAwareOwner owner, Collection<E> c) {
|
||||
this.owner = owner;
|
||||
this.c = c;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return c.toString();
|
||||
}
|
||||
|
||||
public boolean add(E o) {
|
||||
if (c.add(o)) {
|
||||
owner.markAsModified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends E> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<? extends E> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
E o = it.next();
|
||||
if (c.add(o)) {
|
||||
owner.markAsModified();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
if (!c.isEmpty()) {
|
||||
owner.markAsModified();
|
||||
}
|
||||
c.clear();
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return c.contains(o);
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> collection) {
|
||||
return c.containsAll(collection);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return c.isEmpty();
|
||||
}
|
||||
|
||||
public Iterator<E> iterator() {
|
||||
return new ModifyAwareIterator<E>(owner, c.iterator());
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
if (c.remove(o)) {
|
||||
owner.markAsModified();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = collection.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = (Object) it.next();
|
||||
if (c.remove(o)) {
|
||||
owner.markAsModified();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> collection) {
|
||||
boolean changed = false;
|
||||
Iterator<?> it = c.iterator();
|
||||
while (it.hasNext()) {
|
||||
Object o = (Object) it.next();
|
||||
if (!collection.contains(o)) {
|
||||
it.remove();
|
||||
owner.markAsModified();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return c.size();
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return c.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return c.toArray(a);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Wraps an iterator for the purposes of detecting modifications.
|
||||
*/
|
||||
public class ModifyAwareIterator<E> implements Iterator<E> {
|
||||
|
||||
private final ModifyAwareOwner owner;
|
||||
|
||||
private final Iterator<E> it;
|
||||
|
||||
/**
|
||||
* Create with an Owner and the underlying Iterator this wraps.
|
||||
* <p>
|
||||
* The owner is notified of the removals.
|
||||
* </p>
|
||||
*/
|
||||
public ModifyAwareIterator(ModifyAwareOwner owner, Iterator<E> it) {
|
||||
this.owner = owner;
|
||||
this.it = it;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return it.hasNext();
|
||||
}
|
||||
|
||||
public E next() {
|
||||
return it.next();
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
owner.markAsModified();
|
||||
it.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map that is wraps an underlying map for the purpose of detecting changes.
|
||||
*/
|
||||
public class ModifyAwareMap<K,V> implements Map<K,V>, ModifyAwareOwner {
|
||||
|
||||
/**
|
||||
* Dirty flag set when the map has been modified.
|
||||
*/
|
||||
private boolean dirty;
|
||||
|
||||
/**
|
||||
* The underlying map.
|
||||
*/
|
||||
private Map<K,V> map;
|
||||
|
||||
public ModifyAwareMap(Map<K,V> underyling) {
|
||||
this.map = underyling;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMarkedDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsModified() {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
markAsModified();
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
V value = map.remove(key);
|
||||
if (value != null) {
|
||||
markAsModified();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
markAsModified();
|
||||
map.putAll(m);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (!map.isEmpty()) {
|
||||
markAsModified();
|
||||
}
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return new ModifyAwareSet<K>(this, map.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return new ModifyAwareCollection<V>(this, map.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
return new ModifyAwareSet<Map.Entry<K, V>>(this, map.entrySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
/**
|
||||
* Owner object notified when a modification is detected.
|
||||
*/
|
||||
public interface ModifyAwareOwner {
|
||||
|
||||
/**
|
||||
* Return true if the value is considered dirty.
|
||||
*/
|
||||
public boolean isMarkedDirty();
|
||||
|
||||
/**
|
||||
* Marks the object as modified.
|
||||
*/
|
||||
public void markAsModified();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Wraps a Set for the purposes of detecting modifications.
|
||||
*/
|
||||
public class ModifyAwareSet<E> extends ModifyAwareCollection<E> implements Set<E> {
|
||||
|
||||
/**
|
||||
* Create with an Owner that is notified of modifications.
|
||||
*/
|
||||
public ModifyAwareSet(ModifyAwareOwner owner, Set<E> s) {
|
||||
super(owner, s);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
public interface ModifyAwareType {
|
||||
|
||||
public boolean isDirty();
|
||||
}
|
||||
@@ -34,6 +34,17 @@ import com.avaje.ebeaninternal.server.text.json.WriteJsonBuffer;
|
||||
*/
|
||||
public interface ScalarType<T> extends StringParser, StringFormatter, ScalarDataReader<T> {
|
||||
|
||||
/**
|
||||
* Return true if this is a mutable scalar type (like hstore).
|
||||
*/
|
||||
public boolean isMutable();
|
||||
|
||||
/**
|
||||
* For mutable scalarType's return true if the value is dirty.
|
||||
* Non-dirty properties may be excluded from updates.
|
||||
*/
|
||||
public boolean isDirty(Object value);
|
||||
|
||||
/**
|
||||
* Return the default DB column length for this type.
|
||||
* <p>
|
||||
|
||||
@@ -21,6 +21,22 @@ public abstract class ScalarTypeBase<T> implements ScalarType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of mutable false.
|
||||
*/
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default to true.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just return 0.
|
||||
*/
|
||||
public int getLength() {
|
||||
|
||||
@@ -26,6 +26,16 @@ public class ScalarTypeBytesEncrypted implements ScalarType<byte[]> {
|
||||
this.dataEncryptSupport = dataEncryptSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void bind(DataBind b, byte[] value) throws SQLException {
|
||||
value = dataEncryptSupport.encrypt(value);
|
||||
baseType.bind(b, value);
|
||||
|
||||
@@ -22,6 +22,16 @@ public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
|
||||
this.dataEncryptSupport = dataEncryptSupport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return wrapped.isMutable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return wrapped.isDirty(value);
|
||||
}
|
||||
|
||||
public Object readData(DataInput dataInput) throws IOException {
|
||||
return wrapped.readData(dataInput);
|
||||
}
|
||||
|
||||
@@ -103,13 +103,12 @@ public class ScalarTypeEnumStandard {
|
||||
return ((Enum<?>)beanValue).toString();
|
||||
}
|
||||
|
||||
public Object toBeanType(Object dbValue) {
|
||||
if (dbValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Enum.valueOf(enumType, (String)dbValue);
|
||||
}
|
||||
public Object toBeanType(Object dbValue) {
|
||||
if (dbValue == null || dbValue instanceof Enum<?>) {
|
||||
return dbValue;
|
||||
}
|
||||
return Enum.valueOf(enumType, (String) dbValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -183,8 +182,8 @@ public class ScalarTypeEnumStandard {
|
||||
* Convert the db value to the Enum value.
|
||||
*/
|
||||
public Object toBeanType(Object dbValue) {
|
||||
if (dbValue == null) {
|
||||
return null;
|
||||
if (dbValue == null || dbValue instanceof Enum<?>) {
|
||||
return dbValue;
|
||||
}
|
||||
|
||||
int ordinal = ((Integer)dbValue).intValue();
|
||||
|
||||
@@ -72,6 +72,9 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i
|
||||
}
|
||||
|
||||
public Object toBeanType(Object dbValue) {
|
||||
if (dbValue == null || dbValue instanceof Enum<?>) {
|
||||
return dbValue;
|
||||
}
|
||||
return beanDbMap.getBeanValue(dbValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.PostgresPlatform;
|
||||
|
||||
/**
|
||||
* Postgres Hstore type which maps Map<String,String> to a single 'HStore column' in the DB.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class ScalarTypePostgresHstore extends ScalarTypeBase<Map> {
|
||||
|
||||
public static final String KEY = "hstore";
|
||||
|
||||
public static final int HSTORE_TYPE = PostgresPlatform.TYPE_HSTORE;
|
||||
|
||||
public ScalarTypePostgresHstore() {
|
||||
super(Map.class, false, HSTORE_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
if (value instanceof ModifyAwareOwner) {
|
||||
return ((ModifyAwareOwner)value).isMarkedDirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Map read(DataReader dataReader) throws SQLException {
|
||||
|
||||
Object value = dataReader.getObject();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Map == false) {
|
||||
throw new RuntimeException("Expecting Hstore to return as Map but got type "+value.getClass());
|
||||
}
|
||||
return new ModifyAwareMap((Map)value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBind b, Map value) throws SQLException {
|
||||
b.setObject(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toJdbcType(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map toBeanType(Object value) {
|
||||
return (Map)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatValue(Map v) {
|
||||
// TODO format as json
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map parse(String value) {
|
||||
// TODO parse json into map
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map parseDateTime(long dateTime) {
|
||||
throw new RuntimeException("Should never be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDateTimeCapable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readData(DataInput dataInput) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(DataOutput dataOutput, Object v) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,11 +37,21 @@ public class ScalarTypeWrapper<B, S> implements ScalarType<B> {
|
||||
this.nullValue = converter.getNullValue();
|
||||
this.wrapperType = wrapperType;
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
return "ScalarTypeWrapper " + wrapperType + " to " + scalarType.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return scalarType.isMutable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return scalarType.isDirty(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object readData(DataInput dataInput) throws IOException {
|
||||
Object v = scalarType.readData(dataInput);
|
||||
|
||||
@@ -57,4 +57,9 @@ public interface TypeManager {
|
||||
* or String which has limitations).
|
||||
*/
|
||||
public ScalarType<?> createEnumScalarType(Class<?> enumType);
|
||||
|
||||
/**
|
||||
* Find a scalarType using a custom type key. Used for Hstore and similar special types.
|
||||
*/
|
||||
public ScalarType<?> getScalarTypeFromKey(String specialTypeKey);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
|
||||
public class TestFilterWithEnum extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
List<Order> allOrders = Ebean.find(Order.class).findList();
|
||||
|
||||
Filter<Order> filter = Ebean.filter(Order.class);
|
||||
List<Order> newOrders = filter.eq("status", Order.Status.NEW).filter(allOrders);
|
||||
|
||||
Assert.assertNotNull(newOrders);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeEnumStandard.OrdinalEnum;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeEnumStandard.StringEnum;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
|
||||
public class TestEnumToBeanType {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
StringEnum stringEnum = new ScalarTypeEnumStandard.StringEnum(Order.Status.class);
|
||||
|
||||
OrdinalEnum ordinalEnum = new ScalarTypeEnumStandard.OrdinalEnum(Order.Status.class);
|
||||
|
||||
EnumToDbValueMap<?> beanDbMap = EnumToDbValueMap.create(false);
|
||||
beanDbMap.add(Customer.Status.ACTIVE, "A");
|
||||
beanDbMap.add(Customer.Status.NEW, "N");
|
||||
beanDbMap.add(Customer.Status.INACTIVE, "I");
|
||||
|
||||
ScalarTypeEnumWithMapping withMapping = new ScalarTypeEnumWithMapping(beanDbMap, Customer.Status.class, 1);
|
||||
|
||||
|
||||
Object approved = stringEnum.toBeanType(Order.Status.APPROVED);
|
||||
Assert.assertTrue(approved == Order.Status.APPROVED);
|
||||
|
||||
approved = ordinalEnum.toBeanType(Order.Status.APPROVED);
|
||||
Assert.assertTrue(approved == Order.Status.APPROVED);
|
||||
|
||||
Object active = withMapping.toBeanType(Customer.Status.ACTIVE);
|
||||
Assert.assertTrue(active == Customer.Status.ACTIVE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,7 @@ package com.avaje.tests.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -41,7 +40,7 @@ public class TestLimitQuery extends BaseTestCase {
|
||||
.setMaxRows(0)
|
||||
.setFirstRow(3);
|
||||
|
||||
List<Order> list = query.findList();
|
||||
query.findList();
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
boolean hasLimit = sql.indexOf("limit 0") > -1;
|
||||
@@ -67,10 +66,10 @@ public class TestLimitQuery extends BaseTestCase {
|
||||
.setMaxRows(3)
|
||||
.setFirstRow(0);
|
||||
|
||||
List<Order> list = query.findList();
|
||||
query.findList();
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
boolean hasLimit = sql.indexOf("limit 4") > -1;
|
||||
boolean hasLimit = sql.indexOf("limit 3") > -1;
|
||||
boolean hasOffset = sql.indexOf("offset") > -1;
|
||||
|
||||
if (h2Db) {
|
||||
@@ -92,7 +91,7 @@ public class TestLimitQuery extends BaseTestCase {
|
||||
.where().gt("details.id", 0)
|
||||
.query();
|
||||
|
||||
List<Order> list = query.findList();
|
||||
query.findList();
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
boolean hasLimit = sql.indexOf("limit") > -1;
|
||||
@@ -123,7 +122,7 @@ public class TestLimitQuery extends BaseTestCase {
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
boolean hasDetailsJoin = sql.indexOf("join o_order_detail") > -1;
|
||||
boolean hasLimit = sql.indexOf("limit 11") > -1;
|
||||
boolean hasLimit = sql.indexOf("limit 10") > -1;
|
||||
boolean hasSelectedDetails = sql.indexOf("od.id,") > -1;
|
||||
boolean hasDistinct = sql.indexOf("select distinct") > -1;
|
||||
|
||||
@@ -143,7 +142,7 @@ public class TestLimitQuery extends BaseTestCase {
|
||||
|
||||
sql = query.getGeneratedSql();
|
||||
hasDetailsJoin = sql.indexOf("left outer join o_order_detail") > -1;
|
||||
hasLimit = sql.indexOf("limit 11") > -1;
|
||||
hasLimit = sql.indexOf("limit 10") > -1;
|
||||
hasSelectedDetails = sql.indexOf("od.id") > -1;
|
||||
hasDistinct = sql.indexOf("select distinct") > -1;
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ package com.avaje.tests.basic;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -25,6 +24,8 @@ public class TestSharedInstancePropagation extends BaseTestCase {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
Ebean.getServerCacheManager().clearAll();
|
||||
|
||||
Order order = Ebean.find(Order.class)
|
||||
.setAutofetch(false)
|
||||
.setUseCache(true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.avaje.tests.basic.event;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
@@ -19,8 +19,9 @@ public class TestPreInsertValidation extends BaseTestCase {
|
||||
// name with should not be null
|
||||
Ebean.save(e);
|
||||
|
||||
// the save worked
|
||||
// the save worked and name set in preInsert
|
||||
Assert.assertNotNull(e.getId());
|
||||
Assert.assertNotNull(e.getName());
|
||||
|
||||
TWithPreInsert e1 = Ebean.find(TWithPreInsert.class, e.getId());
|
||||
|
||||
@@ -28,4 +29,22 @@ public class TestPreInsertValidation extends BaseTestCase {
|
||||
Ebean.save(e1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatelessUpdate() {
|
||||
|
||||
TWithPreInsert e = new TWithPreInsert();
|
||||
e.setName("BeanForUpdateTest");
|
||||
Ebean.save(e);
|
||||
|
||||
TWithPreInsert bean2 = new TWithPreInsert();
|
||||
bean2.setId(e.getId());
|
||||
bean2.setName("stateless-update-name");
|
||||
bean2.setTitle(null);
|
||||
|
||||
Ebean.update(bean2);
|
||||
|
||||
// title set on preUpdate
|
||||
Assert.assertNotNull(bean2.getTitle());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.avaje.tests.m2m;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.m2m.Permission;
|
||||
import com.avaje.tests.model.m2m.Role;
|
||||
import com.avaje.tests.model.m2m.Tenant;
|
||||
|
||||
public class TestM2MDeleteObjectWithCascadeToM2m extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
Ebean.createUpdate(Permission.class, "delete from Permission").execute();
|
||||
Ebean.createUpdate(Tenant.class, "delete from Tenant").execute();
|
||||
Ebean.createUpdate(Role.class, "delete from Role").execute();
|
||||
|
||||
Tenant tenant1 = new Tenant();
|
||||
tenant1.setName("Tenant");
|
||||
|
||||
Ebean.save(tenant1);
|
||||
|
||||
Permission p1 = new Permission();
|
||||
Permission p2 = new Permission();
|
||||
|
||||
p1.setName("p1");
|
||||
p2.setName("p2");
|
||||
|
||||
Ebean.save(p1);
|
||||
Ebean.save(p2);
|
||||
|
||||
Role role1 = new Role();
|
||||
role1.setName("RoleOne");
|
||||
role1.setTenant(tenant1);
|
||||
|
||||
Set<Permission> permissions = new HashSet<Permission>();
|
||||
List<Permission> permsList = Ebean.find(Permission.class).findList();
|
||||
permissions.addAll(permsList);
|
||||
|
||||
role1.setPermissions(permissions);
|
||||
|
||||
Ebean.save(role1);
|
||||
|
||||
|
||||
List<Tenant> tenantList = Ebean.find(Tenant.class).fetch("roles").findList();
|
||||
List<Role> roleList = Ebean.find(Role.class).fetch("permissions").findList();
|
||||
List<Permission> permissionList = Ebean.find(Permission.class).fetch("roles").findList();
|
||||
|
||||
Assert.assertEquals(1, tenantList.size());
|
||||
Assert.assertEquals(2, permissionList.size());
|
||||
Assert.assertEquals(1, roleList.size());
|
||||
|
||||
Ebean.delete(tenant1);
|
||||
|
||||
List<Tenant> tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList();
|
||||
List<Role> roleList2 = Ebean.find(Role.class).fetch("permissions").findList();
|
||||
List<Permission> permissionList2 = Ebean.find(Permission.class).fetch("roles").findList();
|
||||
|
||||
Assert.assertEquals(0, tenantList2.size());
|
||||
Assert.assertEquals(0, roleList2.size());
|
||||
Assert.assertEquals(2, permissionList2.size());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.avaje.tests.m2m;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.m2m.Permission;
|
||||
import com.avaje.tests.model.m2m.Role;
|
||||
import com.avaje.tests.model.m2m.Tenant;
|
||||
|
||||
public class TestM2mDeleteObject extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
Ebean.createUpdate(Permission.class, "delete from Permission").execute();
|
||||
Ebean.createUpdate(Tenant.class, "delete from Tenant").execute();
|
||||
Ebean.createUpdate(Role.class, "delete from Role").execute();
|
||||
|
||||
Tenant t = new Tenant();
|
||||
t.setName("tenant");
|
||||
|
||||
Ebean.save(t);
|
||||
|
||||
Permission p1 = new Permission();
|
||||
Permission p2 = new Permission();
|
||||
|
||||
p1.setName("p1");
|
||||
p2.setName("p2");
|
||||
|
||||
Ebean.save(p1);
|
||||
|
||||
Ebean.save(p2);
|
||||
|
||||
Role role1 = new Role();
|
||||
role1.setName("role");
|
||||
role1.setTenant(t);
|
||||
|
||||
Set<Permission> permissions = new HashSet<Permission>();
|
||||
List<Permission> permsList = Ebean.find(Permission.class).findList();
|
||||
permissions.addAll(permsList);
|
||||
|
||||
role1.setPermissions(permissions);
|
||||
|
||||
Ebean.save(role1);
|
||||
|
||||
List<Tenant> tenantList = Ebean.find(Tenant.class).fetch("roles").findList();
|
||||
List<Role> roleList = Ebean.find(Role.class).fetch("permissions").findList();
|
||||
List<Permission> permissionList = Ebean.find(Permission.class).fetch("roles").findList();
|
||||
|
||||
Assert.assertEquals(1, tenantList.size());
|
||||
Assert.assertEquals(2, permissionList.size());
|
||||
Assert.assertEquals(1, roleList.size());
|
||||
|
||||
Ebean.delete(role1);
|
||||
|
||||
List<Tenant> tenantList2 = Ebean.find(Tenant.class).fetch("roles").findList();
|
||||
List<Role> roleList2 = Ebean.find(Role.class).fetch("permissions").findList();
|
||||
List<Permission> permissionList2 = Ebean.find(Permission.class).fetch("roles").findList();
|
||||
|
||||
Assert.assertEquals(0, roleList2.size());
|
||||
Assert.assertEquals(1, tenantList2.size());
|
||||
Assert.assertEquals(2, permissionList2.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import javax.persistence.Transient;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import com.avaje.ebean.annotation.EnumMapping;
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
import com.avaje.ebean.annotation.Where;
|
||||
|
||||
/**
|
||||
@@ -28,12 +28,16 @@ public class Customer extends BasicDomain {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* EnumMapping is an Ebean specific mapping for enums.
|
||||
* EnumValue is an Ebean specific mapping for enums.
|
||||
*/
|
||||
@EnumMapping(nameValuePairs="NEW=N,ACTIVE=A,INACTIVE=I")
|
||||
public enum Status {
|
||||
@EnumValue("N")
|
||||
NEW,
|
||||
|
||||
@EnumValue("A")
|
||||
ACTIVE,
|
||||
|
||||
@EnumValue("I")
|
||||
INACTIVE
|
||||
}
|
||||
|
||||
|
||||
@@ -6,34 +6,33 @@ import com.avaje.tests.model.basic.TWithPreInsert;
|
||||
|
||||
public class TWithPreInsertPersistAdapter extends BeanPersistAdapter {
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return TWithPreInsert.class.equals(cls);
|
||||
}
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return TWithPreInsert.class.equals(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
|
||||
TWithPreInsert e = (TWithPreInsert)request.getBean();
|
||||
|
||||
e.setName("aname");
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
|
||||
TWithPreInsert b = (TWithPreInsert)request.getBean();
|
||||
System.out.println("title is Missus:"+b.getTitle());
|
||||
|
||||
//Ebean.refresh(b);
|
||||
request.getEbeanServer().refresh(b);
|
||||
System.out.println("title is Mister:"+b.getTitle());
|
||||
|
||||
return super.preUpdate(request);
|
||||
TWithPreInsert e = (TWithPreInsert) request.getBean();
|
||||
if (e.getName() == null) {
|
||||
e.setName("set on preInsert");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
|
||||
TWithPreInsert b = (TWithPreInsert) request.getBean();
|
||||
System.out.println("preUpdate - title is: " + b.getTitle());
|
||||
if (b.getTitle() == null) {
|
||||
b.setTitle("set on preUpdate");
|
||||
}
|
||||
// request.getEbeanServer().refresh(b);
|
||||
// System.out.println("title is Mister:"+b.getTitle());
|
||||
|
||||
return super.preUpdate(request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.avaje.tests.model.m2m;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
|
||||
/**
|
||||
* The Class Permission.
|
||||
*/
|
||||
@Entity
|
||||
@CacheStrategy(readOnly = true)
|
||||
@Table(name = "mt_permission")
|
||||
public class Permission {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
@ManyToMany(mappedBy = "permissions")
|
||||
private Set<Role> roles;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Set<Role> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<Role> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name:" + name + "id:" + id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.avaje.tests.model.m2m;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
|
||||
/**
|
||||
* The Class Role.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "mt_role")
|
||||
public class Role {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column(length = 50)
|
||||
private String name;
|
||||
|
||||
@ManyToMany(cascade = CascadeType.REMOVE)
|
||||
private Set<Permission> permissions;
|
||||
|
||||
@ManyToOne
|
||||
private Tenant tenant;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Set<Permission> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public void setPermissions(Set<Permission> permissions) {
|
||||
this.permissions = permissions;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Tenant getTenant() {
|
||||
return tenant;
|
||||
}
|
||||
|
||||
public void setTenant(Tenant tenant) {
|
||||
this.tenant = tenant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name:" + name + " id:" + id + " tenant:" + tenant;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.avaje.tests.model.m2m;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
|
||||
/**
|
||||
* The Class Tenant.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "mt_tenant")
|
||||
public class Tenant {
|
||||
|
||||
@Id
|
||||
private UUID id;
|
||||
|
||||
@Column
|
||||
private String name;
|
||||
|
||||
@OneToMany(mappedBy = "tenant", cascade = CascadeType.REMOVE)
|
||||
private Set<Role> roles;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
public Tenant() {
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(UUID id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Set<Role> getRoles() {
|
||||
return roles;
|
||||
}
|
||||
|
||||
public void setRoles(Set<Role> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "name:" + name + " id:" + id;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.avaje.tests.query.orderby;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -7,6 +10,7 @@ import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
import com.avaje.tests.model.basic.OrderDetail;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
|
||||
public class TestOrderByWithMany extends BaseTestCase {
|
||||
@@ -16,6 +20,8 @@ public class TestOrderByWithMany extends BaseTestCase {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
checkWithLazyLoadingOnBuiltInMany();
|
||||
checkWithBuiltInManyBasic();
|
||||
checkWithBuiltInMany();
|
||||
checkAppendId();
|
||||
checkNone();
|
||||
@@ -25,6 +31,42 @@ public class TestOrderByWithMany extends BaseTestCase {
|
||||
checkAlreadyIncluded2();
|
||||
}
|
||||
|
||||
private void checkWithLazyLoadingOnBuiltInMany() {
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class);
|
||||
|
||||
// a query that ensures we are going to lazy load on the details
|
||||
List<Order> orders = query.findList();
|
||||
|
||||
for (Order order : orders) {
|
||||
// invoke lazy loading
|
||||
List<OrderDetail> details = order.getDetails();
|
||||
details.size();
|
||||
}
|
||||
|
||||
// first one is the main query and others are lazy loading queries
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
Assert.assertTrue(loggedSql.size() > 1);
|
||||
|
||||
String lazyLoadSql = loggedSql.get(1);
|
||||
// contains the foreign key back to the parent bean (t0.order_id)
|
||||
Assert.assertTrue(lazyLoadSql.contains("select t0.order_id c0, t0.id"));
|
||||
Assert.assertTrue(lazyLoadSql.contains("order by t0.order_id, t0.id, t0.order_qty, t0.cretime desc"));
|
||||
|
||||
}
|
||||
|
||||
private void checkWithBuiltInManyBasic() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class).fetch("details");
|
||||
query.findList();
|
||||
|
||||
String sql = query.getGeneratedSql();
|
||||
|
||||
Assert.assertTrue(sql.contains("order by t0.id, t1.id asc, t1.order_qty asc, t1.cretime desc"));
|
||||
}
|
||||
|
||||
private void checkWithBuiltInMany() {
|
||||
|
||||
Query<Order> query = Ebean.find(Order.class).fetch("details").order().desc("customer.name");
|
||||
@@ -35,6 +77,7 @@ public class TestOrderByWithMany extends BaseTestCase {
|
||||
|
||||
// t0.id inserted into the middle of the order by
|
||||
Assert.assertTrue(sql.contains("order by t1.name desc, t0.id, t2.id asc"));
|
||||
Assert.assertTrue(sql.contains("t2.id asc, t2.order_qty asc, t2.cretime desc"));
|
||||
}
|
||||
|
||||
private void checkAppendId() {
|
||||
|
||||
@@ -10,7 +10,9 @@ import org.junit.Test;
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Page;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.PagingList;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.tests.model.basic.EBasic;
|
||||
|
||||
@@ -37,40 +39,66 @@ public class TestFindPagedList extends BaseTestCase {
|
||||
transaction.end();
|
||||
}
|
||||
|
||||
PagedList<EBasic> list1 = Ebean.find(EBasic.class)
|
||||
@SuppressWarnings("deprecation")
|
||||
PagingList<EBasic> pagingList = Ebean.find(EBasic.class)
|
||||
.where().like("name", "HelloB0B%")
|
||||
.findPagingList(10);
|
||||
|
||||
Page<EBasic> page7 = pagingList.getPage(7);
|
||||
Assert.assertTrue(page7.hasNext());
|
||||
|
||||
Page<EBasic> page8 = pagingList.getPage(8);
|
||||
Assert.assertFalse(page8.hasNext());
|
||||
|
||||
PagedList<EBasic> page1 = Ebean.find(EBasic.class)
|
||||
.where().like("name", "HelloB0B%")
|
||||
.findPagedList(0, 10);
|
||||
|
||||
list1.loadRowCount();
|
||||
List<EBasic> list = list1.getList();
|
||||
int totalRowCount = list1.getTotalRowCount();
|
||||
int totalPageCount = list1.getTotalPageCount();
|
||||
page1.loadRowCount();
|
||||
List<EBasic> list = page1.getList();
|
||||
|
||||
Assert.assertEquals(10, list.size());
|
||||
Assert.assertEquals(87, totalRowCount);
|
||||
Assert.assertEquals(9, totalPageCount);
|
||||
Assert.assertEquals(87, page1.getTotalRowCount());
|
||||
Assert.assertEquals(9, page1.getTotalPageCount());
|
||||
Assert.assertEquals(true, page1.hasNext());
|
||||
|
||||
PagedList<EBasic> list2 = Ebean.find(EBasic.class)
|
||||
|
||||
PagedList<EBasic> page1b = Ebean.find(EBasic.class)
|
||||
.where().like("name", "HelloB0B%")
|
||||
.findPagedList(0, 10);
|
||||
|
||||
// Same as page1 but without initial loadRowCount() call
|
||||
List<EBasic> list1B = page1b.getList();
|
||||
Assert.assertEquals(10, list1B.size());
|
||||
Assert.assertEquals(87, page1b.getTotalRowCount());
|
||||
Assert.assertEquals(9, page1b.getTotalPageCount());
|
||||
Assert.assertEquals(true, page1.hasNext());
|
||||
|
||||
|
||||
PagedList<EBasic> page2 = Ebean.find(EBasic.class)
|
||||
.where().like("name", "HelloB0B%")
|
||||
.findPagedList(4, 10);
|
||||
|
||||
list = list2.getList();
|
||||
list = page2.getList();
|
||||
|
||||
Assert.assertEquals(10, list2.getList().size());
|
||||
Assert.assertEquals(87, list2.getTotalRowCount());
|
||||
Assert.assertEquals(9, list2.getTotalPageCount());
|
||||
Assert.assertEquals(10, page2.getList().size());
|
||||
Assert.assertEquals(87, page2.getTotalRowCount());
|
||||
Assert.assertEquals(9, page2.getTotalPageCount());
|
||||
Assert.assertEquals(true, page2.hasNext());
|
||||
|
||||
PagedList<EBasic> list3 = Ebean.find(EBasic.class)
|
||||
PagedList<EBasic> page3 = Ebean.find(EBasic.class)
|
||||
.where().like("name", "HelloB0B%")
|
||||
.findPagedList(8, 10);
|
||||
|
||||
Future<Integer> rowCount = list3.getFutureRowCount();
|
||||
list = list3.getList();
|
||||
Future<Integer> rowCount = page3.getFutureRowCount();
|
||||
list = page3.getList();
|
||||
|
||||
Assert.assertEquals(Integer.valueOf(87), rowCount.get());
|
||||
Assert.assertEquals(7, list3.getList().size());
|
||||
Assert.assertEquals(87, list3.getTotalRowCount());
|
||||
Assert.assertEquals(9, list3.getTotalPageCount());
|
||||
Assert.assertEquals(7, page3.getList().size());
|
||||
Assert.assertEquals(87, page3.getTotalRowCount());
|
||||
Assert.assertEquals(9, page3.getTotalPageCount());
|
||||
Assert.assertEquals(false, page3.hasNext());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.tests.rawsql;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@@ -8,8 +9,8 @@ import org.junit.Test;
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.FetchConfig;
|
||||
import com.avaje.ebean.Page;
|
||||
import com.avaje.ebean.PagingList;
|
||||
import com.avaje.ebean.FutureRowCount;
|
||||
import com.avaje.ebean.PagedList;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.ebean.RawSql;
|
||||
import com.avaje.ebean.RawSqlBuilder;
|
||||
@@ -19,52 +20,86 @@ import com.avaje.tests.model.basic.ResetBasicData;
|
||||
public class TestRawSqlOrmQuery extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
public void test() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql =
|
||||
RawSqlBuilder
|
||||
.parse("select r.id, r.name from o_customer r ")
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name")
|
||||
.create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
query.where().ilike("name", "r%");
|
||||
|
||||
query.fetch("contacts", new FetchConfig().query());
|
||||
query.filterMany("contacts").gt("lastName", "b");
|
||||
|
||||
List<Customer> list = query.findList();
|
||||
Assert.assertNotNull(list);
|
||||
}
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql = RawSqlBuilder.parse("select r.id, r.name from o_customer r ")
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name").create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
query.where().ilike("name", "r%");
|
||||
|
||||
query.fetch("contacts", new FetchConfig().query());
|
||||
query.filterMany("contacts").gt("lastName", "b");
|
||||
|
||||
List<Customer> list = query.findList();
|
||||
Assert.assertNotNull(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaging() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql =
|
||||
RawSqlBuilder
|
||||
.parse("select r.id, r.name from o_customer r ")
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name")
|
||||
.create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
PagingList<Customer> pagingList = query.findPagingList(10);
|
||||
Page<Customer> page = pagingList.getPage(0);
|
||||
List<Customer> list = page.getList();
|
||||
|
||||
System.out.println(page);
|
||||
System.out.println(list);
|
||||
|
||||
for (Customer customer : list) {
|
||||
customer.getCretime();
|
||||
}
|
||||
|
||||
public void testFirstRowsMaxRows() throws InterruptedException, ExecutionException {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql =
|
||||
RawSqlBuilder
|
||||
.parse("select r.id, r.name from o_customer r ")
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name")
|
||||
.create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
|
||||
int initialRowCount = query.findRowCount();
|
||||
|
||||
query.setFirstRow(1);
|
||||
query.setMaxRows(2);
|
||||
List<Customer> list = query.findList();
|
||||
|
||||
int rowCount = query.findRowCount();
|
||||
FutureRowCount<Customer> futureRowCount = query.findFutureRowCount();
|
||||
|
||||
Assert.assertEquals(initialRowCount, rowCount);
|
||||
Assert.assertEquals(initialRowCount, futureRowCount.get().intValue());
|
||||
|
||||
// check that lazy loading still executes
|
||||
for (Customer customer : list) {
|
||||
customer.getCretime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaging() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql = RawSqlBuilder.parse("select r.id, r.name from o_customer r ")
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name")
|
||||
.create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
|
||||
int initialRowCount = query.findRowCount();
|
||||
|
||||
PagedList<Customer> page = query.findPagedList(0, 2);
|
||||
|
||||
List<Customer> list = page.getList();
|
||||
int rowCount = page.getTotalRowCount();
|
||||
|
||||
Assert.assertEquals(2, list.size());
|
||||
Assert.assertEquals(initialRowCount, rowCount);
|
||||
|
||||
// check that lazy loading executes
|
||||
for (Customer customer : list) {
|
||||
customer.getCretime();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,15 @@ public class TestRawSqlPositionedParams extends BaseTestCase {
|
||||
Assert.assertNotNull(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_unparsed() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
RawSql rawSql = RawSqlBuilder
|
||||
.unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
|
||||
.columnMapping("r.id", "id").columnMapping("r.name", "name").create();
|
||||
.columnMapping("r.id", "id")
|
||||
.columnMapping("r.name", "name").create();
|
||||
|
||||
Query<Customer> query = Ebean.find(Customer.class);
|
||||
query.setRawSql(rawSql);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.avaje.tests.rawsql;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.FetchConfig;
|
||||
import com.avaje.ebean.RawSql;
|
||||
import com.avaje.ebean.Transaction;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
|
||||
public class TestRawSqlWithResultSet extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() throws SQLException {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
// Transaction supplies our jdbc Connection
|
||||
Transaction txn = Ebean.beginTransaction();
|
||||
|
||||
PreparedStatement pstmt = null;
|
||||
|
||||
try {
|
||||
pstmt = txn.getConnection().prepareStatement("select id, name, billing_address_id from o_customer");
|
||||
|
||||
// ResultSet will be closed by Ebean
|
||||
ResultSet resultSet = pstmt.executeQuery();
|
||||
|
||||
RawSql rawSql = new RawSql(resultSet, "id", "name", "billingAddress.id");
|
||||
|
||||
List<Customer> list = Ebean.find(Customer.class)
|
||||
.setRawSql(rawSql)
|
||||
// also test a secondary query join
|
||||
.fetch("billingAddress", new FetchConfig().query())
|
||||
.findList();
|
||||
|
||||
for (Customer customer : list) {
|
||||
System.out.println("id:"+customer.getId()+" name:"+customer.getName()+" billingAddress:"+customer.getBillingAddress());
|
||||
}
|
||||
|
||||
} finally {
|
||||
close(pstmt);
|
||||
txn.end();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void close(Statement stmt) {
|
||||
|
||||
if (stmt != null) {
|
||||
try {
|
||||
stmt.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.avaje.ebeantest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.UnsynchronizedAppenderBase;
|
||||
|
||||
/**
|
||||
* Helper that can collect the SQL that is logged via SLF4J.
|
||||
* <p>
|
||||
* Used {@link #start()} and {@link #stop()} to collect the logged messages that contain the
|
||||
* executed SQL statements.
|
||||
* <p>
|
||||
* Internally this uses a Logback Appender to collect messages for org.avaje.ebean.SQL.
|
||||
*/
|
||||
public class LoggedSqlCollector {
|
||||
|
||||
private static BasicAppender basicAppender = new BasicAppender();
|
||||
|
||||
static {
|
||||
|
||||
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
|
||||
basicAppender.setContext(lc);
|
||||
|
||||
Logger logger = (Logger) LoggerFactory.getLogger("org.avaje.ebean.SQL");
|
||||
logger.addAppender(basicAppender);
|
||||
logger.setLevel(Level.TRACE);
|
||||
logger.setAdditive(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start collection of the logged SQL statements.
|
||||
*/
|
||||
public static List<String> start() {
|
||||
return basicAppender.collectStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop collection of the logged SQL statements return the list of captured messages that contain
|
||||
* the SQL.
|
||||
*/
|
||||
public static List<String> stop() {
|
||||
return basicAppender.collectEnd();
|
||||
}
|
||||
|
||||
private static class BasicAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
|
||||
|
||||
List<String> messages = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
protected void append(ILoggingEvent eventObject) {
|
||||
if (started) {
|
||||
messages.add(eventObject.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start collection.
|
||||
*/
|
||||
List<String> collectStart() {
|
||||
List<String> tempMessages = messages;
|
||||
messages = new ArrayList<String>();
|
||||
// set started flag
|
||||
start();
|
||||
return tempMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* End collection.
|
||||
*/
|
||||
List<String> collectEnd() {
|
||||
// set stopped state
|
||||
stop();
|
||||
List<String> tempMessages = messages;
|
||||
messages = new ArrayList<String>();
|
||||
return tempMessages;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user