Compare commits

...
Author SHA1 Message Date
Rob Bygrave 723ae6bffc [maven-release-plugin] prepare release avaje-ebeanorm-4.1.0 2014-06-26 19:38:48 +12:00
Rob Bygrave 33b173dd55 Bump pom to 4.1.0-SNAPSHOT 2014-06-26 19:37:15 +12:00
Rob Bygrave 7966d74eb3 Just tweaking some very common string appending with buffer size prediction 2014-06-26 01:15:48 +12:00
Rob Bygrave 28cb2e210d Merge branch 'master' of github.com:ebean-orm/avaje-ebeanorm 2014-06-26 00:37:40 +12:00
Rob Bygrave 1109d412e7 Fix for #154 - @OrderBy on a @OneToMany property is not used when lazy loading 2014-06-26 00:37:31 +12:00
Rob Bygrave 3e33ece65a No effective change - add protected modified 2014-06-26 00:12:55 +12:00
Rob Bygrave 1dd200fba2 Merge pull request #153 from waffle-iron/master
waffle.io Badge
2014-06-25 09:16:10 +12:00
Making GitHub Delicious. f0ea977f91 add waffle.io badge 2014-06-24 15:13:51 -06:00
Rob Bygrave e830d8efc7 Additional test in TestOrderByWithMany 2014-06-24 00:17:11 +12:00
Rob Bygrave 0b2ba2a82e Fix for #151 - IllegalArgumentException using enum in filter - toBeanType() throws 2014-06-24 00:04:32 +12:00
Rob Bygrave df526b8f3c Fix for #149 - OneToMany with nested ManyToMany deletion bug 2014-06-23 23:34:22 +12:00
Rob Bygrave 8fe1f32e78 No effective change - code reformat on PersistRequestBean 2014-06-21 00:03:28 +12:00
Rob Bygrave c5ea93fb8e Fix for #150 - Stateless update - property changed in BeanPersistAdapter
preUpdate not included in update
2014-06-20 23:49:29 +12:00
Rob Bygrave dd83bc4d26 Test BeanPersistAdapter with stateless update 2014-06-19 02:03:59 +12:00
Rob Bygrave 9b4b7a1a01 Opps, part of #108 2014-06-19 02:03:14 +12:00
Rob Bygrave 548fa745d6 Fix for #108 - ENH (443) : Postgres HStore support 2014-06-19 01:35:07 +12:00
Rob Bygrave 7d83e3f49e Fix for #97 - ENH: (406) Add support for Postgres Serial type 2014-06-13 23:48:45 +12:00
Rob Bygrave 974284bf8a Addition to fix for #139 - DDL constraint generation when table with schema 2014-06-13 02:38:14 +12:00
Rob Bygrave b446f00cd8 Addition to fix for #139 - DDL constraint generation when table with schema 2014-06-12 23:48:40 +12:00
Rob Bygrave a4095b4ae5 Fix for #142 - ENH: Add support for using RawSql with user supplied ResultSet 2014-06-12 21:11:14 +12:00
Rob Bygrave af6dbd0dbe Fix for #140 - Add some support for RawSql findRowCount, remove BeanCollection.hasMoreRows() 2014-06-10 23:52:05 +12:00
Rob Bygrave db17ddd069 No effective change - tidy up test for findPagedList 2014-06-10 23:10:27 +12:00
Rob Bygrave b6b85225e7 Fix for #139 - DDL constraint generation when table with schema 2014-06-10 21:13:16 +12:00
Rob Bygrave b57abfa732 Update README.md 2014-05-30 03:28:25 +12:00
Rob Bygrave 4a4927b1f3 Update README.md 2014-05-30 03:27:38 +12:00
Rob Bygrave 6ac34bec97 Update README.md 2014-05-30 03:26:59 +12:00
Rob Bygrave 82725e6a98 [maven-release-plugin] prepare for next development iteration 2014-05-30 03:07:28 +12:00
Rob Bygrave a744c8b375 [maven-release-plugin] prepare release avaje-ebeanorm-4.0.4 2014-05-30 03:07:04 +12:00
Rob Bygrave 5e2a34de39 removing maven-javadoc-plugin section from pom as it breaks oss sonatype
deployment
2014-05-30 03:05:16 +12:00
Rob Bygrave 8a9ab39a01 Fix for #138 Issue with lazy loading after a findIterate query 2014-05-30 02:50:51 +12:00
Rob Bygrave 515256abfd pom - javadoc + remove unused enhancement section 2014-05-30 01:36:20 +12:00
Rob Bygrave 84e5a6145e Fix for #137 - transaction.setBatchFlushOnQuery(false) not working for
some queries
2014-05-30 01:35:19 +12:00
Rob Bygrave 1f5618d0e3 Fix for #136 - Minor - not log extra SQL into log when using jdbc batch
mode
2014-05-30 01:32:44 +12:00
Rob Bygrave b1de821817 Fix for #135 Make Model abstract, change db() and db(server) to be
static methods.
2014-05-30 01:30:57 +12:00
Rob Bygrave f4dceb97b0 [maven-release-plugin] prepare for next development iteration 2014-05-27 22:28:35 +12:00
85 changed files with 2651 additions and 1105 deletions
+5 -2
View File
@@ -1,12 +1,15 @@
[![Stories in Ready](https://badge.waffle.io/ebean-orm/avaje-ebeanorm.png?label=ready&title=Ready)](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>
+24 -14
View File
@@ -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>
+31 -4
View File
@@ -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(&quot;Roberto&quot;);
* customer.save();
*
* Customer otherCustomer = new Customer();
* otherCustomer.setName(&quot;Franko&quot;);
* 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);
}
+51 -3
View File
@@ -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() {
@@ -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;
}
}
@@ -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;
}
}
}