提交 1f7ed616 编写于 作者: G gaohongtao 提交者: gaoht

fix #36 cannot reuse ShardingPreparedStatement

fix #114 ShardingPreparedStatement batch execution parse sql
上级 fe49c45d
......@@ -23,6 +23,7 @@ import com.dangdang.ddframe.rdb.sharding.executor.event.DMLExecutionEventBus;
import com.dangdang.ddframe.rdb.sharding.executor.event.DQLExecutionEvent;
import com.dangdang.ddframe.rdb.sharding.executor.event.DQLExecutionEventBus;
import com.dangdang.ddframe.rdb.sharding.executor.event.EventExecutionType;
import com.dangdang.ddframe.rdb.sharding.executor.wrapper.BatchPreparedStatementExecutorWrapper;
import com.dangdang.ddframe.rdb.sharding.executor.wrapper.PreparedStatementExecutorWrapper;
import com.dangdang.ddframe.rdb.sharding.metrics.MetricsContext;
import com.google.common.base.Optional;
......@@ -194,6 +195,80 @@ public final class PreparedStatementExecutor {
return result;
}
/**
* 执行批量接口.
*
* @return 每个
*/
public int[] executeBatch() {
Context context = MetricsContext.start("ShardingPreparedStatement-executeUpdate");
postExecutionEvents();
final boolean isExceptionThrown = ExecutorExceptionHandler.isExceptionThrown();
final Map<String, Object> dataMap = ExecutorDataMap.getDataMap();
try {
if (1 == preparedStatementExecutorWrappers.size()) {
return executeBatchInternal((BatchPreparedStatementExecutorWrapper) preparedStatementExecutorWrappers.iterator().next(), isExceptionThrown, dataMap);
}
return executorEngine.execute(preparedStatementExecutorWrappers, new ExecuteUnit<PreparedStatementExecutorWrapper, int[]>() {
@Override
public int[] execute(final PreparedStatementExecutorWrapper input) throws Exception {
return executeBatchInternal((BatchPreparedStatementExecutorWrapper) input, isExceptionThrown, dataMap);
}
}, new MergeUnit<int[], int[]>() {
@Override
public int[] merge(final List<int[]> results) {
if (null == results) {
return new int[]{0};
}
int length = 0;
for (int[] array : results) {
length += array.length;
}
int[] result = new int[length];
int pos = 0;
for (int[] array : results) {
System.arraycopy(array, 0, result, pos, array.length);
pos += array.length;
}
return result;
}
});
} finally {
MetricsContext.stop(context);
}
}
private int[] executeBatchInternal(final BatchPreparedStatementExecutorWrapper batchPreparedStatementExecutorWrapper, final boolean isExceptionThrown, final Map<String, Object> dataMap) {
int[] result;
ExecutorExceptionHandler.setExceptionThrown(isExceptionThrown);
ExecutorDataMap.setDataMap(dataMap);
try {
result = batchPreparedStatementExecutorWrapper.getPreparedStatement().executeBatch();
} catch (final SQLException ex) {
postBatchExecutionEventsAfterExecution(batchPreparedStatementExecutorWrapper, EventExecutionType.EXECUTE_FAILURE, Optional.of(ex));
ExecutorExceptionHandler.handleException(ex);
return null;
}
postBatchExecutionEventsAfterExecution(batchPreparedStatementExecutorWrapper);
return result;
}
private void postBatchExecutionEventsAfterExecution(final BatchPreparedStatementExecutorWrapper batchPreparedStatementExecutorWrapper) {
postBatchExecutionEventsAfterExecution(batchPreparedStatementExecutorWrapper, EventExecutionType.EXECUTE_SUCCESS, Optional.<SQLException>absent());
}
private void postBatchExecutionEventsAfterExecution(
final BatchPreparedStatementExecutorWrapper batchPreparedStatementExecutorWrapper, final EventExecutionType eventExecutionType, final Optional<SQLException> exp) {
for (DMLExecutionEvent each : batchPreparedStatementExecutorWrapper.getDmlExecutionEvents()) {
each.setEventExecutionType(eventExecutionType);
each.setExp(exp);
DMLExecutionEventBus.post(each);
}
}
private void postExecutionEvents() {
for (PreparedStatementExecutorWrapper each : preparedStatementExecutorWrappers) {
if (each.getDMLExecutionEvent().isPresent()) {
......
/*
* Copyright 1999-2015 dangdang.com.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* </p>
*/
package com.dangdang.ddframe.rdb.sharding.executor.wrapper;
import com.dangdang.ddframe.rdb.sharding.executor.event.DMLExecutionEvent;
import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit;
import lombok.Getter;
import java.sql.PreparedStatement;
import java.util.LinkedList;
import java.util.List;
/**
* 批量操作执行上下文.
* 批量操作只支持DML语句,故只包含DML操作的事件.
*
* @author gaohongtao
*/
@Getter
public class BatchPreparedStatementExecutorWrapper extends PreparedStatementExecutorWrapper {
private final List<DMLExecutionEvent> dmlExecutionEvents = new LinkedList<>();
public BatchPreparedStatementExecutorWrapper(final PreparedStatement preparedStatement, final List<Object> parameters, final SQLExecutionUnit sqlExecutionUnit) {
super(preparedStatement, parameters, sqlExecutionUnit);
}
}
......@@ -21,6 +21,7 @@ import com.dangdang.ddframe.rdb.sharding.executor.event.DMLExecutionEvent;
import com.dangdang.ddframe.rdb.sharding.executor.event.DQLExecutionEvent;
import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import lombok.Getter;
import java.sql.PreparedStatement;
......@@ -31,7 +32,7 @@ import java.util.List;
*
* @author zhangliang
*/
public final class PreparedStatementExecutorWrapper extends AbstractExecutorWrapper {
public class PreparedStatementExecutorWrapper extends AbstractExecutorWrapper {
@Getter
private final PreparedStatement preparedStatement;
......@@ -45,10 +46,10 @@ public final class PreparedStatementExecutorWrapper extends AbstractExecutorWrap
super(sqlExecutionUnit);
this.preparedStatement = preparedStatement;
if (isDML()) {
dmlExecutionEvent = Optional.of(new DMLExecutionEvent(getSqlExecutionUnit().getDataSource(), getSqlExecutionUnit().getSql(), parameters));
dmlExecutionEvent = Optional.of(new DMLExecutionEvent(getSqlExecutionUnit().getDataSource(), getSqlExecutionUnit().getSql(), Lists.newArrayList(parameters)));
dqlExecutionEvent = Optional.absent();
} else if (isDQL()) {
dqlExecutionEvent = Optional.of(new DQLExecutionEvent(getSqlExecutionUnit().getDataSource(), getSqlExecutionUnit().getSql(), parameters));
dqlExecutionEvent = Optional.of(new DQLExecutionEvent(getSqlExecutionUnit().getDataSource(), getSqlExecutionUnit().getSql(), Lists.newArrayList(parameters)));
dmlExecutionEvent = Optional.absent();
} else {
dmlExecutionEvent = Optional.absent();
......
......@@ -18,24 +18,23 @@
package com.dangdang.ddframe.rdb.sharding.jdbc;
import com.dangdang.ddframe.rdb.sharding.executor.PreparedStatementExecutor;
import com.dangdang.ddframe.rdb.sharding.executor.wrapper.BatchPreparedStatementExecutorWrapper;
import com.dangdang.ddframe.rdb.sharding.executor.wrapper.PreparedStatementExecutorWrapper;
import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractPreparedStatementAdapter;
import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory;
import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext;
import com.dangdang.ddframe.rdb.sharding.router.PreparedSQLRouter;
import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit;
import com.dangdang.ddframe.rdb.sharding.router.SQLRouteResult;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 支持分片的预编译语句对象.
......@@ -45,9 +44,9 @@ import java.util.List;
*/
public final class ShardingPreparedStatement extends AbstractPreparedStatementAdapter {
private final String sql;
private final PreparedSQLRouter preparedSQLRouter;
private final List<PreparedStatementExecutorWrapper> cachedRoutedPreparedStatements = new LinkedList<>();
private final Map<PreparedStatement, PreparedStatementExecutorWrapper> cachedRoutePreparedStatementMap = new HashMap<>();
private Integer autoGeneratedKeys;
......@@ -55,10 +54,6 @@ public final class ShardingPreparedStatement extends AbstractPreparedStatementAd
private String[] columnNames;
private boolean hasExecuted;
private final List<List<Object>> batchParameters = new ArrayList<>();
public ShardingPreparedStatement(final ShardingConnection shardingConnection, final String sql) throws SQLException {
this(shardingConnection, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT);
}
......@@ -69,9 +64,9 @@ public final class ShardingPreparedStatement extends AbstractPreparedStatementAd
}
public ShardingPreparedStatement(final ShardingConnection shardingConnection,
final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
super(shardingConnection, resultSetType, resultSetConcurrency, resultSetHoldability);
this.sql = sql;
preparedSQLRouter = shardingConnection.getShardingContext().getSqlRouteEngine().prepareSQL(sql);
}
public ShardingPreparedStatement(final ShardingConnection shardingConnection, final String sql, final int autoGeneratedKeys) throws SQLException {
......@@ -91,82 +86,84 @@ public final class ShardingPreparedStatement extends AbstractPreparedStatementAd
@Override
public ResultSet executeQuery() throws SQLException {
hasExecuted = true;
setCurrentResultSet(ResultSetFactory.getResultSet(
new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), getRoutedPreparedStatements()).executeQuery(), getMergeContext()));
return getCurrentResultSet();
ResultSet rs;
try {
rs = ResultSetFactory.getResultSet(
new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).executeQuery(), getMergeContext());
} finally {
clearRouteContext();
}
setCurrentResultSet(rs);
return rs;
}
@Override
public int executeUpdate() throws SQLException {
hasExecuted = true;
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), getRoutedPreparedStatements()).executeUpdate();
try {
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).executeUpdate();
} finally {
clearRouteContext();
}
}
@Override
public boolean execute() throws SQLException {
hasExecuted = true;
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), getRoutedPreparedStatements()).execute();
try {
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routeSQL()).execute();
} finally {
clearRouteContext();
}
}
private void clearRouteContext() throws SQLException {
clearParameters();
setCurrentResultSet(null);
}
@Override
public void addBatch() throws SQLException {
batchParameters.add(Lists.newArrayList(getParameters()));
getParameters().clear();
try {
for (PreparedStatementExecutorWrapper each : routeSQL()) {
each.getPreparedStatement().addBatch();
BatchPreparedStatementExecutorWrapper wrapper;
if (cachedRoutePreparedStatementMap.containsKey(each.getPreparedStatement())) {
wrapper = (BatchPreparedStatementExecutorWrapper) cachedRoutePreparedStatementMap.get(each.getPreparedStatement());
} else {
wrapper = new BatchPreparedStatementExecutorWrapper(each.getPreparedStatement(), getParameters(), each.getSqlExecutionUnit());
cachedRoutePreparedStatementMap.put(each.getPreparedStatement(), wrapper);
}
if (each.getDMLExecutionEvent().isPresent()) {
wrapper.getDmlExecutionEvents().add(each.getDMLExecutionEvent().get());
}
}
} finally {
clearRouteContext();
}
}
@Override
public void clearBatch() throws SQLException {
batchParameters.clear();
cachedRoutePreparedStatementMap.clear();
clearRouteContext();
}
@Override
public int[] executeBatch() throws SQLException {
hasExecuted = true;
int[] result = new int[batchParameters.size()];
int i = 0;
for (List<Object> each : batchParameters) {
List<PreparedStatementExecutorWrapper> routePreparedStatements = routeSQL(each);
cachedRoutedPreparedStatements.addAll(routePreparedStatements);
result[i++] = new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), routePreparedStatements).executeUpdate();
}
return result;
}
private List<PreparedStatementExecutorWrapper> getRoutedPreparedStatements() throws SQLException {
if (!hasExecuted) {
return Collections.emptyList();
}
routeIfNeed();
return cachedRoutedPreparedStatements;
}
@Override
public List<? extends Statement> getRoutedStatements() throws SQLException {
return Lists.transform(getRoutedPreparedStatements(), new Function<PreparedStatementExecutorWrapper, Statement>() {
@Override
public Statement apply(final PreparedStatementExecutorWrapper input) {
return input.getPreparedStatement();
}
});
}
private void routeIfNeed() throws SQLException {
if (!cachedRoutedPreparedStatements.isEmpty()) {
return;
try {
return new PreparedStatementExecutor(getShardingConnection().getShardingContext().getExecutorEngine(), cachedRoutePreparedStatementMap.values()).executeBatch();
} finally {
clearBatch();
}
cachedRoutedPreparedStatements.addAll(routeSQL(getParameters()));
}
private List<PreparedStatementExecutorWrapper> routeSQL(final List<Object> parameters) throws SQLException {
private List<PreparedStatementExecutorWrapper> routeSQL() throws SQLException {
List<Object> parameters = getParameters();
List<PreparedStatementExecutorWrapper> result = new ArrayList<>();
SQLRouteResult sqlRouteResult = getShardingConnection().getShardingContext().getSqlRouteEngine().route(sql, parameters);
SQLRouteResult sqlRouteResult = preparedSQLRouter.route(parameters);
MergeContext mergeContext = sqlRouteResult.getMergeContext();
setMergeContext(mergeContext);
for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) {
PreparedStatement preparedStatement = generatePrepareStatement(getShardingConnection().getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql());
PreparedStatement preparedStatement = (PreparedStatement) getStatement(getShardingConnection().getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql());
replayMethodsInvocation(preparedStatement);
setParameters(preparedStatement, parameters);
result.add(new PreparedStatementExecutorWrapper(preparedStatement, parameters, each));
......@@ -174,7 +171,7 @@ public final class ShardingPreparedStatement extends AbstractPreparedStatementAd
return result;
}
private PreparedStatement generatePrepareStatement(final Connection conn, final String shardingSql) throws SQLException {
protected PreparedStatement generateStatement(final Connection conn, final String shardingSql) throws SQLException {
if (null != autoGeneratedKeys) {
return conn.prepareStatement(shardingSql, autoGeneratedKeys);
}
......
......@@ -22,7 +22,6 @@ import com.dangdang.ddframe.rdb.sharding.executor.wrapper.StatementExecutorWrapp
import com.dangdang.ddframe.rdb.sharding.jdbc.adapter.AbstractStatementAdapter;
import com.dangdang.ddframe.rdb.sharding.merger.ResultSetFactory;
import com.dangdang.ddframe.rdb.sharding.parser.result.merger.MergeContext;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.SQLStatementType;
import com.dangdang.ddframe.rdb.sharding.router.SQLExecutionUnit;
import com.dangdang.ddframe.rdb.sharding.router.SQLRouteResult;
import com.google.common.base.Charsets;
......@@ -62,13 +61,13 @@ public class ShardingStatement extends AbstractStatementAdapter {
@Getter
private final int resultSetHoldability;
@Getter(AccessLevel.PROTECTED)
private final Map<HashCode, Statement> cachedRoutedStatements = new HashMap<>();
@Getter(AccessLevel.PROTECTED)
@Setter(AccessLevel.PROTECTED)
private MergeContext mergeContext;
@Getter(AccessLevel.PROTECTED)
@Setter(AccessLevel.PROTECTED)
private ResultSet currentResultSet;
......@@ -147,25 +146,30 @@ public class ShardingStatement extends AbstractStatementAdapter {
SQLRouteResult sqlRouteResult = shardingConnection.getShardingContext().getSqlRouteEngine().route(sql);
mergeContext = sqlRouteResult.getMergeContext();
for (SQLExecutionUnit each : sqlRouteResult.getExecutionUnits()) {
result.addStatement(new StatementExecutorWrapper(generateStatement(each.getSql(), each.getDataSource(), sqlRouteResult.getSqlStatementType()), each));
Statement statement = getStatement(shardingConnection.getConnection(each.getDataSource(), sqlRouteResult.getSqlStatementType()), each.getSql());
replayMethodsInvocation(statement);
result.addStatement(new StatementExecutorWrapper(statement, each));
}
return result;
}
private Statement generateStatement(final String sql, final String dataSourceName, final SQLStatementType sqlStatementType) throws SQLException {
HashCode hashCode = Hashing.md5().newHasher().putString(sql, Charsets.UTF_8).putString(dataSourceName, Charsets.UTF_8).hash();
protected Statement getStatement(final Connection connection, final String sql) throws SQLException {
HashCode hashCode = Hashing.md5().newHasher().putInt(connection.hashCode()).putString(sql, Charsets.UTF_8).hash();
if (cachedRoutedStatements.containsKey(hashCode)) {
return cachedRoutedStatements.get(hashCode);
}
Connection connection = shardingConnection.getConnection(dataSourceName, sqlStatementType);
Statement statement = generateStatement(connection, sql);
cachedRoutedStatements.put(hashCode, statement);
return statement;
}
protected Statement generateStatement(final Connection connection, final String sql) throws SQLException {
Statement result;
if (0 == resultSetHoldability) {
result = connection.createStatement(resultSetType, resultSetConcurrency);
} else {
result = connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
}
replayMethodsInvocation(result);
cachedRoutedStatements.put(hashCode, result);
return result;
}
......
......@@ -17,14 +17,14 @@
package com.dangdang.ddframe.rdb.sharding.parser.result.router;
import java.util.ArrayList;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
/**
* 条件对象.
*
......@@ -42,6 +42,8 @@ public final class Condition {
private final List<Comparable<?>> values = new ArrayList<>();
private final List<Integer> valueIndices = new ArrayList<>();
/**
* 列对象.
*
......
......@@ -17,16 +17,16 @@
package com.dangdang.ddframe.rdb.sharding.parser.result.router;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOperator;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column;
import com.google.common.base.Optional;
import lombok.ToString;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 条件对象上下文.
*
......@@ -85,4 +85,20 @@ public final class ConditionContext {
public Collection<Condition> getAllConditions() {
return conditions.values();
}
public void setNewConditionValue(final List<Object> parameters) {
for (Condition each : conditions.values()) {
if (each.getValueIndices().isEmpty()) {
continue;
}
for (int i = 0; i < each.getValueIndices().size(); i++) {
Object value = parameters.get(each.getValueIndices().get(i));
if (value instanceof Comparable<?>) {
each.getValues().set(i, (Comparable<?>) value);
} else {
each.getValues().set(i, "");
}
}
}
}
}
......@@ -24,7 +24,9 @@ import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.visitor.SQLEvalVisitor;
import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils;
import com.alibaba.druid.util.JdbcUtils;
import com.dangdang.ddframe.rdb.sharding.constants.DatabaseType;
import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult;
import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn;
......@@ -37,9 +39,11 @@ import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.BinaryOp
import com.dangdang.ddframe.rdb.sharding.parser.result.router.Condition.Column;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.Table;
import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLEvalVisitor;
import com.dangdang.ddframe.rdb.sharding.util.SQLUtil;
import com.google.common.base.Optional;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
......@@ -136,9 +140,9 @@ public final class ParseContext {
if (!column.isPresent() || !shardingColumns.contains(column.get().getColumnName())) {
return;
}
List<Comparable<?>> values = new ArrayList<>(valueExprList.size());
List<ValuePair> values = new ArrayList<>(valueExprList.size());
for (SQLExpr each : valueExprList) {
Comparable<?> evalValue = evalExpression(databaseType, each, parameters);
ValuePair evalValue = evalExpression(databaseType, each, parameters);
if (null != evalValue) {
values.add(evalValue);
}
......@@ -164,13 +168,13 @@ public final class ParseContext {
if (!shardingColumns.contains(column.getColumnName())) {
return;
}
Comparable<?> value = evalExpression(databaseType, valueExpr, parameters);
ValuePair value = evalExpression(databaseType, valueExpr, parameters);
if (null != value) {
addCondition(column, operator, Collections.<Comparable<?>>singletonList(value));
addCondition(column, operator, Collections.singletonList(value));
}
}
private void addCondition(final Column column, final BinaryOperator operator, final List<Comparable<?>> values) {
private void addCondition(final Column column, final BinaryOperator operator, final List<ValuePair> valuePairs) {
Optional<Condition> optionalCondition = currentConditionContext.find(column.getTableName(), column.getColumnName(), operator);
Condition condition;
// TODO 待讨论
......@@ -180,23 +184,48 @@ public final class ParseContext {
condition = new Condition(column, operator);
currentConditionContext.add(condition);
}
condition.getValues().addAll(values);
for (ValuePair each : valuePairs) {
condition.getValues().add(each.value);
if (each.paramIndex > -1) {
condition.getValueIndices().add(each.paramIndex);
}
}
}
private Comparable<?> evalExpression(final DatabaseType databaseType, final SQLObject sqlObject, final List<Object> parameters) {
private ValuePair evalExpression(final DatabaseType databaseType, final SQLObject sqlObject, final List<Object> parameters) {
if (sqlObject instanceof SQLMethodInvokeExpr) {
// TODO 解析函数中的sharingValue不支持
return null;
}
Object result = SQLEvalVisitorUtils.eval(databaseType.name().toLowerCase(), sqlObject, parameters, false);
if (null == result) {
SQLEvalVisitor visitor;
switch (databaseType.name().toLowerCase()) {
case JdbcUtils.MYSQL:
case JdbcUtils.H2:
visitor = new MySQLEvalVisitor();
break;
default:
visitor = SQLEvalVisitorUtils.createEvalVisitor(databaseType.name());
}
visitor.setParameters(parameters);
sqlObject.accept(visitor);
Object value = SQLEvalVisitorUtils.getValue(sqlObject);
if (null == value) {
// TODO 对于NULL目前解析为空字符串,此处待考虑解决方法
return null;
}
if (result instanceof Comparable<?>) {
return (Comparable<?>) result;
Comparable<?> finalValue;
if (value instanceof Comparable<?>) {
finalValue = (Comparable<?>) value;
} else {
finalValue = "";
}
// TODO 对于NULL目前解析为空字符串,此处待考虑解决方法
return "";
Integer index = (Integer) sqlObject.getAttribute(MySQLEvalVisitor.EVAL_VAR_INDEX);
if (null == index) {
index = -1;
}
return new ValuePair(finalValue, index);
}
private Optional<Column> getColumn(final SQLExpr expr) {
......@@ -375,4 +404,12 @@ public final class ParseContext {
}
selectItems.add(rawItemExpr);
}
@RequiredArgsConstructor
private static class ValuePair {
private final Comparable<?> value;
private final Integer paramIndex;
}
}
/*
* Copyright 1999-2015 dangdang.com.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* </p>
*/
package com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlEvalVisitorImpl;
import java.util.Map;
/**
* MySQL变量中提取参数值与编号.
*
* @author gaohongtao.
*/
public class MySQLEvalVisitor extends MySqlEvalVisitorImpl {
public static final String EVAL_VAR_INDEX = "EVAL_VAR_INDEX";
@Override
public boolean visit(final SQLVariantRefExpr x) {
if (!"?".equals(x.getName())) {
return false;
}
Map<String, Object> attributes = x.getAttributes();
int varIndex = x.getIndex();
if (varIndex == -1 || getParameters().size() <= varIndex) {
return false;
}
if (attributes.containsKey(EVAL_VALUE)) {
return false;
}
Object value = getParameters().get(varIndex);
if (value == null) {
value = EVAL_VALUE_NULL;
}
attributes.put(EVAL_VALUE, value);
attributes.put(EVAL_VAR_INDEX, varIndex);
return false;
}
}
/*
* Copyright 1999-2015 dangdang.com.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* </p>
*/
package com.dangdang.ddframe.rdb.sharding.router;
import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResult;
import com.dangdang.ddframe.rdb.sharding.parser.result.router.ConditionContext;
import lombok.RequiredArgsConstructor;
import java.util.List;
/**
* 预解析功能的SQL路由器.
*
* @author gaohongtao
*/
@RequiredArgsConstructor
public class PreparedSQLRouter {
private final String logicSql;
private final SQLRouteEngine engine;
private SQLParsedResult sqlParsedResult;
/**
* 使用参数进行SQL路由.
* 当第一次路由时进行SQL解析,之后的路由复用第一次的解析结果.
*
* @param parameters SQL中的参数
* @return 路由结果
*/
public SQLRouteResult route(final List<Object> parameters) {
if (null == sqlParsedResult) {
sqlParsedResult = engine.parseSQL(logicSql, parameters);
} else {
for (ConditionContext each : sqlParsedResult.getConditionContexts()) {
each.setNewConditionValue(parameters);
}
}
return engine.routeSQL(sqlParsedResult);
}
}
......@@ -80,14 +80,24 @@ public final class SQLRouteEngine {
return routeSQL(parseSQL(logicSql, parameters));
}
private SQLParsedResult parseSQL(final String logicSql, final List<Object> parameters) {
/**
* 预解析SQL路由.
*
* @param logicSql 逻辑SQL
* @return 预解析SQL路由器
*/
public PreparedSQLRouter prepareSQL(final String logicSql) {
return new PreparedSQLRouter(logicSql, this);
}
SQLParsedResult parseSQL(final String logicSql, final List<Object> parameters) {
Context context = MetricsContext.start("Parse SQL");
SQLParsedResult result = SQLParserFactory.create(databaseType, logicSql, parameters, shardingRule.getAllShardingColumns()).parse();
MetricsContext.stop(context);
return result;
}
private SQLRouteResult routeSQL(final SQLParsedResult parsedResult) {
SQLRouteResult routeSQL(final SQLParsedResult parsedResult) {
Context context = MetricsContext.start("Route SQL");
SQLRouteResult result = new SQLRouteResult(parsedResult.getRouteContext().getSqlStatementType(), parsedResult.getMergeContext());
for (ConditionContext each : parsedResult.getConditionContexts()) {
......
......@@ -17,17 +17,6 @@
package com.dangdang.ddframe.rdb.integrate;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import com.dangdang.ddframe.rdb.sharding.constants.DatabaseType;
import org.apache.commons.dbcp.BasicDataSource;
import org.dbunit.DatabaseUnitException;
......@@ -42,13 +31,24 @@ import org.dbunit.operation.DatabaseOperation;
import org.h2.tools.RunScript;
import org.junit.Before;
import javax.sql.DataSource;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.dbunit.Assertion.assertEquals;
public abstract class AbstractDBUnitTest {
protected static final DatabaseType CURRENT_DB_TYPE = DatabaseType.H2;
private static final Map<String, DataSource> DATA_SOURCES = new HashMap<>();
protected static final Map<String, DataSource> DATA_SOURCES = new HashMap<>();
private final DataBaseEnvironment dbEnv = new DataBaseEnvironment(CURRENT_DB_TYPE);
......
......@@ -77,7 +77,7 @@ public abstract class AbstractShardingBothForStatementWithDMLTest extends Abstra
for (int i = 10; i < 20; i++) {
for (int j = 0; j < 10; j++) {
try (Connection connection = shardingDataSource.getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertThat(stmt.executeUpdate(String.format(sql, i * 100 + j, i)), is(1));
}
}
......
......@@ -41,7 +41,7 @@ public final class StaticShardingBothForStatementWithDMLTest extends AbstractSha
public void assertUpdateWithoutShardingValue() throws SQLException, DatabaseUnitException {
String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertThat(stmt.executeUpdate(String.format(sql, "updated", "init")), is(100));
}
assertDataSet("update", "updated");
......@@ -51,7 +51,7 @@ public final class StaticShardingBothForStatementWithDMLTest extends AbstractSha
public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException {
String sql = "DELETE `t_order` WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertThat(stmt.executeUpdate(String.format(sql, "init")), is(100));
}
assertDataSet("delete", "init");
......
......@@ -47,7 +47,7 @@ public final class ShardingMasterSlaveForStatementWithDMLTest extends AbstractSh
assertSelectBeforeUpdate();
String sql = "UPDATE `t_order` SET `status` = '%s' WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertThat(stmt.executeUpdate(String.format(sql, "updated", "init_master")), is(100));
}
assertDataSet("update", "updated");
......@@ -57,7 +57,7 @@ public final class ShardingMasterSlaveForStatementWithDMLTest extends AbstractSh
private void assertSelectBeforeUpdate() throws SQLException, DatabaseUnitException {
String sql = "SELECT * FROM `t_order` WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertFalse(stmt.executeQuery(String.format(sql, "updated")).next());
}
}
......@@ -65,7 +65,7 @@ public final class ShardingMasterSlaveForStatementWithDMLTest extends AbstractSh
private void assertSelectAfterUpdate() throws SQLException, DatabaseUnitException {
String sql = "SELECT * FROM `t_order` WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertTrue(stmt.executeQuery(String.format(sql, "updated")).next());
}
}
......@@ -74,7 +74,7 @@ public final class ShardingMasterSlaveForStatementWithDMLTest extends AbstractSh
public void assertDeleteWithoutShardingValue() throws SQLException, DatabaseUnitException {
String sql = "DELETE `t_order` WHERE `status` = '%s'";
try (Connection connection = getShardingDataSource().getConnection()) {
Statement stmt = connection.prepareStatement(sql);
Statement stmt = connection.createStatement();
assertThat(stmt.executeUpdate(String.format(sql, "init_master")), is(100));
}
assertDataSet("delete", "init");
......
......@@ -17,21 +17,20 @@
package com.dangdang.ddframe.rdb.sharding.jdbc;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest;
import com.mysql.jdbc.Statement;
import org.junit.Before;
import org.junit.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Before;
import org.junit.Test;
import com.dangdang.ddframe.rdb.integrate.db.AbstractShardingDataBasesOnlyDBUnitTest;
import com.mysql.jdbc.Statement;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public final class ShardingPreparedStatementTest extends AbstractShardingDataBasesOnlyDBUnitTest {
......@@ -52,6 +51,14 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
ResultSet resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(40L));
preparedStatement.setString(1, "null");
resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(0L));
preparedStatement.setString(1, "init");
resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(40L));
}
}
......@@ -64,6 +71,12 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
ResultSet resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(40L));
resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(40L));
resultSet = preparedStatement.executeQuery();
assertTrue(resultSet.next());
assertThat(resultSet.getLong(1), is(40L));
}
}
......@@ -75,6 +88,10 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
preparedStatement.setString(1, "init");
assertThat(preparedStatement.executeUpdate(), is(40));
preparedStatement.setString(1, "null");
assertThat(preparedStatement.executeUpdate(), is(0));
preparedStatement.setString(1, "init");
assertThat(preparedStatement.executeUpdate(), is(0));
}
}
......@@ -85,6 +102,8 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
Connection connection = shardingDataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
assertThat(preparedStatement.executeUpdate(), is(40));
assertThat(preparedStatement.executeUpdate(), is(0));
assertThat(preparedStatement.executeUpdate(), is(0));
}
}
......@@ -98,6 +117,14 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
assertTrue(preparedStatement.execute());
assertTrue(preparedStatement.getResultSet().next());
assertThat(preparedStatement.getResultSet().getLong(1), is(40L));
preparedStatement.setString(1, "null");
assertTrue(preparedStatement.execute());
assertTrue(preparedStatement.getResultSet().next());
assertThat(preparedStatement.getResultSet().getLong(1), is(0L));
preparedStatement.setString(1, "init");
assertTrue(preparedStatement.execute());
assertTrue(preparedStatement.getResultSet().next());
assertThat(preparedStatement.getResultSet().getLong(1), is(40L));
}
}
......@@ -108,6 +135,8 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
Connection connection = shardingDataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
assertFalse(preparedStatement.execute());
assertFalse(preparedStatement.execute());
assertFalse(preparedStatement.execute());
}
}
......@@ -203,6 +232,14 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
preparedStatement.setInt(2, 12);
preparedStatement.setString(3, "BATCH");
preparedStatement.addBatch();
preparedStatement.setInt(1, 3111);
preparedStatement.setInt(2, 21);
preparedStatement.setString(3, "BATCH");
preparedStatement.addBatch();
preparedStatement.setInt(1, 3112);
preparedStatement.setInt(2, 22);
preparedStatement.setString(3, "BATCH");
preparedStatement.addBatch();
int[] result = preparedStatement.executeBatch();
for (int each : result) {
assertThat(each, is(1));
......@@ -210,7 +247,7 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
}
}
@Test(expected = IllegalStateException.class)
@Test
public void assertClearBatch() throws SQLException {
String sql = "INSERT INTO `t_order`(`order_id`, `user_id`, `status`) VALUES (?,?,?)";
try (
......@@ -221,7 +258,8 @@ public final class ShardingPreparedStatementTest extends AbstractShardingDataBas
preparedStatement.setString(3, "BATCH");
preparedStatement.addBatch();
preparedStatement.clearBatch();
preparedStatement.executeBatch();
int[] result = preparedStatement.executeBatch();
assertThat(result.length, is(0));
}
}
}
......@@ -146,6 +146,9 @@ public abstract class AbstractBaseParseTest {
return input.getValueWithType();
}
}));
if (null != each.getValueIndices()) {
condition.getValueIndices().addAll(each.getValueIndices());
}
result.add(condition);
}
return result;
......
......@@ -22,6 +22,7 @@ import com.dangdang.ddframe.rdb.sharding.parser.mysql.MySQLPreparedStatementForT
import com.dangdang.ddframe.rdb.sharding.parser.mysql.MySQLStatementTest;
import com.dangdang.ddframe.rdb.sharding.parser.mysql.OrParseTest;
import com.dangdang.ddframe.rdb.sharding.parser.result.SQLParsedResultTest;
import com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql.MySQLEvalVisitorTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
......@@ -32,7 +33,8 @@ import org.junit.runners.Suite;
MySQLPreparedStatementForOneParameterTest.class,
MySQLPreparedStatementForTowParametersTest.class,
OrParseTest.class,
UnsupportedParseTest.class
UnsupportedParseTest.class,
MySQLEvalVisitorTest.class,
})
public class AllParserTests {
}
......@@ -17,15 +17,14 @@
package com.dangdang.ddframe.rdb.sharding.parser.jaxb;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
......@@ -43,4 +42,7 @@ public final class Condition {
@XmlElement(name = "value")
private List<Value> values;
@XmlElement(name = "valueIndices")
private List<Integer> valueIndices;
}
......@@ -54,7 +54,7 @@ public final class SQLParsedResultTest {
+ "sqlStatementType=null, "
+ "sqlBuilder=SELECT * FROM [Token(order)]), "
+ "conditionContexts=[ConditionContext(conditions={Condition.Column(columnName=id, tableName=order)=Condition(column=Condition.Column(columnName=id, tableName=order), "
+ "operator=IN, values=[1, 2, 3])})], "
+ "operator=IN, values=[1, 2, 3], valueIndices=[])})], "
+ "mergeContext=MergeContext("
+ "orderByColumns=[OrderByColumn(super=AbstractSortableColumn(owner=Optional.absent(), "
+ "name=Optional.of(id), alias=Optional.of(a), orderByType=DESC), index=Optional.absent(), columnIndex=0)], "
......
/*
* Copyright 1999-2015 dangdang.com.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* </p>
*/
package com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.visitor.SQLEvalVisitorUtils;
import com.google.common.collect.Lists;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
public class MySQLEvalVisitorTest {
@Test
public void testVisit() throws Exception {
SQLVariantRefExpr expr = new SQLVariantRefExpr("?");
expr.setIndex(1);
MySQLEvalVisitor visitor = new MySQLEvalVisitor();
visitor.setParameters(Lists.<Object>newArrayList(1 ,2));
expr.accept(visitor);
assertThat((Integer) SQLEvalVisitorUtils.getValue(expr), is(2));
assertThat((Integer) expr.getAttribute(MySQLEvalVisitor.EVAL_VAR_INDEX), is(1));
}
@Test
public void testVisitErrorIndex() throws Exception {
SQLVariantRefExpr expr = new SQLVariantRefExpr("?");
expr.setIndex(2);
MySQLEvalVisitor visitor = new MySQLEvalVisitor();
visitor.setParameters(Lists.<Object>newArrayList(1 ,2));
expr.accept(visitor);
assertThat(SQLEvalVisitorUtils.getValue(expr), nullValue());
}
@Test
public void testVisitNotOverride() throws Exception {
SQLVariantRefExpr expr = new SQLVariantRefExpr("?");
expr.setIndex(1);
expr.getAttributes().put(MySQLEvalVisitor.EVAL_VALUE, "test");
MySQLEvalVisitor visitor = new MySQLEvalVisitor();
visitor.setParameters(Lists.<Object>newArrayList(1 ,2));
expr.accept(visitor);
assertThat((String) SQLEvalVisitorUtils.getValue(expr), is("test"));
}
@Test
public void testVisitWrongName() throws Exception {
SQLVariantRefExpr expr = new SQLVariantRefExpr("");
expr.setIndex(1);
MySQLEvalVisitor visitor = new MySQLEvalVisitor();
visitor.setParameters(Lists.<Object>newArrayList(1 ,2));
expr.accept(visitor);
assertThat(SQLEvalVisitorUtils.getValue(expr), nullValue());
}
@Test
public void testVisitNullValue() throws Exception {
SQLVariantRefExpr expr = new SQLVariantRefExpr("?");
expr.setIndex(1);
MySQLEvalVisitor visitor = new MySQLEvalVisitor();
visitor.setParameters(Lists.<Object>newArrayList(1 ,null));
expr.accept(visitor);
assertThat(SQLEvalVisitorUtils.getValue(expr), is(MySQLEvalVisitor.EVAL_VALUE_NULL));
}
}
\ No newline at end of file
......@@ -8,6 +8,7 @@
<condition-context>
<condition column-name="order_id" table-name="order" operator="IN">
<value value="1" type="java.lang.Integer" />
<valueIndices>0</valueIndices>
</condition>
</condition-context>
</condition-contexts>
......
......@@ -8,6 +8,7 @@
<condition-context>
<condition column-name="order_id" table-name="order" operator="EQUAL">
<value value="1" type="java.lang.Integer" />
<valueIndices>0</valueIndices>
</condition>
<condition column-name="state" table-name="order" operator="EQUAL">
<value value="RUNNING" type="java.lang.String" />
......
......@@ -10,6 +10,7 @@
<value value="1" type="int" />
<value value="2" type="int" />
<value value="3" type="int" />
<valueIndices>0</valueIndices>
</condition>
</condition-context>
</condition-contexts>
......
......@@ -8,6 +8,7 @@
<condition-context>
<condition column-name="order_id" table-name="order" operator="EQUAL">
<value value="1" type="java.lang.Integer" />
<valueIndices>0</valueIndices>
</condition>
</condition-context>
</condition-contexts>
......
......@@ -9,6 +9,8 @@
<condition column-name="order_id" table-name="order" operator="BETWEEN">
<value value="1" type="int" />
<value value="2" type="int" />
<valueIndices>0</valueIndices>
<valueIndices>1</valueIndices>
</condition>
</condition-context>
</condition-contexts>
......
......@@ -8,6 +8,11 @@ weight = 1
## 1.3.2-SNAPSHOT
### 缺陷修正
1. [ISSUE #36](https://github.com/dangdangdotcom/sharding-jdbc/issues/36) ShardingPreparedStatement无法反复设置参数
1. [ISSUE #114](https://github.com/dangdangdotcom/sharding-jdbc/issues/114) ShardingPreparedStatement执行批处理任务时,反复解析sql导致oom
## 1.3.1
### 功能提升
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册