#398 - ENH: Support delete using a Query instance

This commit is contained in:
Robin Bygrave
2015-08-29 00:58:47 +12:00
parent ccba5d08ef
commit 1c23720605
21 changed files with 407 additions and 24 deletions
+22 -8
View File
@@ -1,13 +1,5 @@
package com.avaje.ebean;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
@@ -17,6 +9,13 @@ import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides the API for fetching and saving beans to a particular DataSource.
* <p>
@@ -1114,6 +1113,21 @@ public interface EbeanServer {
@Nullable
<T> T findUnique(Query<T> query, Transaction transaction);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
@@ -107,6 +107,18 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete();
/**
* Execute the query iterating over the results.
*
+12
View File
@@ -743,6 +743,18 @@ public interface Query<T> extends Serializable {
*/
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete();
/**
* Return the count of entities this query should return.
* <p>
@@ -84,7 +84,12 @@ public interface SpiQuery<T> extends Query<T> {
/**
* A subquery used as part of a where clause.
*/
SUBQUERY
SUBQUERY,
/**
* Delete query.
*/
DELETE,
}
enum TemporalMode {
@@ -272,6 +277,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
NaturalKeyBindParam getNaturalKeyBindParam();
/**
* Set the query to be a delete query.
*/
void setDelete();
/**
* Set the query to select the id property only.
*/
@@ -1338,6 +1338,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> int delete(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
try {
request.initTransIfRequired();
return request.delete();
} finally {
request.endTransIfRequired();
}
}
public <T> FutureRowCount<T> findFutureRowCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
@@ -39,4 +39,8 @@ public interface OrmQueryEngine {
*/
<T> BeanIdList findIds(OrmQueryRequest<T> request);
/**
* Execute the query as a delete statement.
*/
<T> int delete(OrmQueryRequest<T> request);
}
@@ -218,7 +218,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public boolean isFindById() {
return query.getType() == Type.BEAN;
}
/**
* Execute the query as a delete.
*/
public int delete() {
return queryEngine.delete(this);
}
/**
* Execute the query as findById.
*/
@@ -47,6 +47,11 @@ public interface SpiOrmQueryRequest<T> {
*/
void endTransIfRequired();
/**
* Execute the query as a delete.
*/
int delete();
/**
* Execute the query as findById.
*/
@@ -677,6 +677,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
}
/**
* Return the "delete by id" sql.
*/
public String getDeleteByIdInSql() {
return deleteByIdInSql;
}
/**
* Return SQL that can be used to delete a list of Id's without any optimistic
* concurrency checking.
@@ -218,6 +218,11 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
throw new RuntimeException("filterMany not allowed on Junction expression list");
}
@Override
public int delete() {
return exprList.delete();
}
@Override
public Query<T> asOf(Timestamp asOf) {
return exprList.asOf(asOf);
@@ -83,6 +83,47 @@ public class CQueryBuilder {
return sb.toString();
}
/**
* Build the delete query.
*/
public <T> CQueryDelete buildDeleteQuery(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
query.setDelete();
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
String sql = queryPlan.getSql();
return new CQueryDelete(request, predicates, sql);
}
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query));
boolean includeJoins = sqlTree.isIncludeJoins();
String sql;
if (!includeJoins) {
// simple - delete from table ...
sql = buildSql("delete", request, predicates, sqlTree).getSql();
} else {
// wrap as - delete from table where id in (select id ...)
sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
}
// cache the query plan
queryPlan = new CQueryPlan(request, sql, sqlTree, false, false, predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryDelete(request, predicates, sql);
}
/**
* Build the row count query.
*/
@@ -0,0 +1,135 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.type.DataBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* Executes the delete query.
*/
public class CQueryDelete {
private static final Logger logger = LoggerFactory.getLogger(CQueryDelete.class);
private final OrmQueryRequest<?> request;
private final BeanDescriptor<?> desc;
private final SpiQuery<?> query;
/**
* Where clause predicates.
*/
private final CQueryPredicates predicates;
/**
* The final sql that is generated.
*/
private final String sql;
/**
* The statement used to create the resultSet.
*/
private PreparedStatement pstmt;
private String bindLog;
private int executionTimeMicros;
private int rowCount;
/**
* Create the Sql select based on the request.
*/
public CQueryDelete(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
this.request = request;
this.query = request.getQuery();
this.sql = sql;
query.setGeneratedSql(sql);
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
}
/**
* Return a summary description of this query.
*/
public String getSummary() {
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(80);
sb.append("Delete exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
.append("] type[").append(desc.getName())
.append("] predicates[").append(predicates.getLogWhereSql())
.append("] bind[").append(bindLog).append("]");
return sb.toString();
}
/**
* Return the bind log.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return the generated sql.
*/
public String getGeneratedSql() {
return sql;
}
/**
* Execute the query returning the row count.
*/
public int delete() throws SQLException {
long startNano = System.nanoTime();
try {
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(new DataBind(pstmt));
rowCount = pstmt.executeUpdate();
long exeNano = System.nanoTime() - startNano;
executionTimeMicros = (int) exeNano / 1000;
return rowCount;
} finally {
close();
}
}
/**
* Close the resources.
*/
private void close() {
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error(null, e);
}
}
}
@@ -53,6 +53,27 @@ public class CQueryEngine {
return queryBuilder.buildQuery(request);
}
public <T> int delete(OrmQueryRequest<T> request) {
CQueryDelete query = queryBuilder.buildDeleteQuery(request);
try {
int rows = query.delete();
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
}
request.logSql(logSql);
}
return rows;
} catch (SQLException e) {
throw CQuery.createPersistenceException(e, request.getTransaction(), query.getBindLog(), query.getGeneratedSql());
}
}
/**
* Build and execute the find Id's query.
*/
@@ -47,6 +47,12 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
}
}
public <T> int delete(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
return queryEngine.delete(request);
}
public <T> int findRowCount(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
@@ -41,11 +41,13 @@ public class SqlTree {
*/
private final String inheritanceWhereSql;
private final boolean includeJoins;
/**
* Create the SqlSelectClause.
*/
public SqlTree(String summary, SqlTreeNode rootNode, String selectSql, String fromSql, String inheritanceWhereSql,
BeanProperty[] encryptedProps, BeanPropertyAssocMany<?> manyProperty, Set<String> includes) {
BeanProperty[] encryptedProps, BeanPropertyAssocMany<?> manyProperty, Set<String> includes, boolean includeJoins) {
this.summary = summary;
this.rootNode = rootNode;
@@ -55,6 +57,7 @@ public class SqlTree {
this.encryptedProps = encryptedProps;
this.manyProperty = manyProperty;
this.includes = includes;
this.includeJoins = includeJoins;
}
/**
@@ -69,6 +72,14 @@ public class SqlTree {
this.encryptedProps = null;
this.manyProperty = null;
this.includes = null;
this.includeJoins = false; //not valid for rawSql
}
/**
* Return true if the query includes joins (not valid for rawSql).
*/
public boolean isIncludeJoins() {
return includeJoins;
}
/**
@@ -205,4 +205,11 @@ public class SqlTreeAlias {
private String nextManyWhereTableAlias() {
return "u" + (++manyWhereCounter);
}
/**
* Return true if there are joins included in the query.
*/
public boolean isIncludeJoins() {
return !aliasMap.isEmpty() || !manyWhereAliasMap.isEmpty();
}
}
@@ -1,17 +1,5 @@
package com.avaje.ebeaninternal.server.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebeaninternal.api.ManyWhereJoins;
import com.avaje.ebeaninternal.api.PropertyJoin;
import com.avaje.ebeaninternal.api.SpiQuery;
@@ -27,6 +15,17 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Factory for SqlTree.
@@ -138,8 +137,10 @@ public class SqlTreeBuilder {
encryptedProps = ctx.getEncryptedProps();
}
boolean includeJoins = (alias == null) ? false : alias.isIncludeJoins();
return new SqlTree(summary.toString(), rootNode, selectSql, fromSql, inheritanceWhereSql, encryptedProps,
manyProperty, queryDetail.getIncludes());
manyProperty, queryDetail.getIncludes(), includeJoins);
}
private String buildSelectClause() {
@@ -413,6 +413,17 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
detail.convertManyFetchJoinsToQueryJoins(beanDescriptor, lazyLoadManyPath, allowOne, queryBatch);
}
/**
* Setup to be a delete query.
*/
public void setDelete() {
// unset any paging and select on the id in the case where the query
// includes joins and we use - delete ... where id in (...)
maxRows = 0;
firstRow = 0;
setSelectId();
}
/**
* Set the select clause to select the Id property.
*/
@@ -948,6 +959,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return this;
}
@Override
public int delete() {
return server.delete(this, null);
}
public List<Object> findIds() {
// a copy of this query is made in the server
// as the query needs to modified (so we modify
@@ -158,6 +158,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.apply(pathProperties);
}
@Override
public int delete() {
return query.delete();
}
@Override
public FutureIds<T> findFutureIds() {
return query.findFutureIds();