mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#398 - ENH: Support delete using a Query instance
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user