package jef.database;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import jef.common.Entry;
import jef.common.PairSO;
import jef.common.log.LogUtil;
import jef.database.Session.UpdateContext;
import jef.database.dialect.ColumnType;
import jef.database.dialect.DatabaseDialect;
import jef.database.dialect.type.ColumnMapping;
import jef.database.dialect.type.VersionSupportColumn;
import jef.database.jsqlparser.parser.ParseException;
import jef.database.jsqlparser.visitor.Expression;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.query.JoinElement;
import jef.database.query.JpqlExpression;
import jef.database.query.ParameterProvider.MapProvider;
import jef.database.query.Query;
import jef.database.query.SqlContext;
import jef.database.query.SqlExpression;
import jef.database.routing.PartitionResult;
import jef.database.support.SqlLog;
import jef.database.wrapper.clause.BindSql;
import jef.database.wrapper.clause.UpdateClause;
import jef.database.wrapper.executor.DbTask;
import jef.database.wrapper.variable.BindVariableContext;
import jef.database.wrapper.variable.ConstantVariable;
import jef.database.wrapper.variable.UpdateVairable;
import jef.database.wrapper.variable.Variable;
import jef.tools.Assert;
import jef.tools.StringUtils;
import jef.tools.reflect.BeanWrapper;
/**
* 基本数据库操作
*
* @author jiyi
*
*/
public abstract class UpdateProcessor {
/**
* 执行更新操作
*
* @param db
* @param obj
* @param setValues
* @param whereValues
* @param p
* @param parseCost
* @return
* @throws SQLException
*/
abstract int processUpdate0(OperateTarget db, IQueryableEntity obj, UpdateClause setValues, BindSql whereValues, PartitionResult site, SqlLog log) throws SQLException;
/**
* 形成update语句
*
* @param obj
* 更新请求
* @param dynamic
* 动态更新标记
* @return SQL片段
*/
abstract UpdateClause toUpdateClause(IQueryableEntity obj, PartitionResult[] prs, boolean dynamic) throws SQLException;
/**
* 形成update语句
*
* @param obj
* 更新请求
* @param dynamic
* 动态更新标记
* @return SQL片段
*/
abstract UpdateClause toUpdateClauseBatch(IQueryableEntity obj, PartitionResult[] prs, boolean dynamic) throws SQLException;
/**
* 形成whrer部分语句
*
* @param joinElement
* @param context
* @param update
* @param profile
* @return
*/
abstract BindSql toWhereClause(JoinElement joinElement, SqlContext context, UpdateContext update, DatabaseDialect profile);
static UpdateProcessor get(DatabaseDialect profile, DbClient db) {
return new PreparedImpl(db.preProcessor);
}
protected SqlProcessor processor;
UpdateProcessor(SqlProcessor parent) {
this.processor = parent;
}
final static class PreparedImpl extends UpdateProcessor {
public PreparedImpl(SqlProcessor db) {
super(db);
}
int processUpdate0(OperateTarget db, IQueryableEntity obj, UpdateClause setValues, BindSql whereValues, PartitionResult site, SqlLog log) throws SQLException {
int result = 0;
for (String tablename : site.getTables()) {
tablename = DbUtils.escapeColumn(db.getProfile(), tablename);
String updateSql = StringUtils.concat("update ", tablename, " set ", setValues.getSql(), whereValues.getSql());
log.ensureCapacity(updateSql.length() + 150);
log.append(updateSql).append(db);
PreparedStatement psmt = null;
try {
psmt = db.prepareStatement(updateSql);
int updateTimeout = ORMConfig.getInstance().getUpdateTimeout();
if (updateTimeout > 0) {
psmt.setQueryTimeout(updateTimeout);
}
BindVariableContext context = new BindVariableContext(psmt, db.getProfile(), log);
context.setVariables(obj.getQuery(), setValues.getVariables(), whereValues.getBind());
psmt.execute();
int currentUpdateCount = psmt.getUpdateCount();
result += currentUpdateCount;
obj.clearUpdate();
} catch (SQLException e) {
DbUtils.processError(e, tablename, db);
db.releaseConnection();
throw e;
} finally {
log.output();
if (psmt != null)
psmt.close();
}
}
db.releaseConnection();
return result;
}
/**
* 返回2个参数 第一个为带?的SQL String 第二个为 update语句中所用的Field
*
* @param obj
* @return
*/
@SuppressWarnings("unchecked")
public UpdateClause toUpdateClauseBatch(IQueryableEntity obj, PartitionResult[] prs, boolean dynamic) {
DatabaseDialect profile = processor.getProfile(prs);
UpdateClause result = new UpdateClause();
Map<Field, Object> map = obj.getUpdateValueMap();
Map.Entry<Field, Object>[] fields;
ITableMetadata meta = MetaHolder.getMeta(obj);
if (dynamic) {
fields = map.entrySet().toArray(new Map.Entry[map.size()]);
moveLobFieldsToLast(fields, meta);
// 增加时间戳自动更新的列
VersionSupportColumn[] autoUpdateTime = meta.getAutoUpdateColumnDef();
if (autoUpdateTime != null) {
for (VersionSupportColumn tm : autoUpdateTime) {
if (!map.containsKey(tm.field())) {
tm.processAutoUpdate(profile, result);
}
}
}
} else {
fields = getAllFieldValues(meta, map, BeanWrapper.wrap(obj), profile);
}
boolean safeMerge=ORMConfig.getInstance().isSafeMerge();
for (Map.Entry<Field, Object> e : fields) {
Field field = e.getKey();
ColumnMapping column=meta.getColumnDef(field);
if(column!=null && column.isNotUpdate()){
continue;
}
Object value = e.getValue();
String columnName = meta.getColumnName(field, profile, true);
if (value instanceof SqlExpression) {
String sql = ((SqlExpression) value).getText();
if (obj.hasQuery()) {
Map<String, Object> attrs = ((Query<?>) obj.getQuery()).getAttributes();
if (attrs != null && attrs.size() > 0) {
try {
Expression ex = DbUtils.parseExpression(sql);
Entry<String, List<Object>> fieldInExpress = NamedQueryConfig.applyParam(ex, new MapProvider(attrs));
if (fieldInExpress.getValue().size() > 0) {
sql = fieldInExpress.getKey();
for (Object v : fieldInExpress.getValue()) {
result.addField(new ConstantVariable(v));
}
}
} catch (ParseException e1) {
// 如果解析异常就不修改sql语句
}
}
}
result.addEntry(columnName, sql);
} else if (value instanceof JpqlExpression) {
JpqlExpression je = (JpqlExpression) value;
if (!je.isBind())
je.setBind(obj.getQuery());
PairSO<List<Variable>> entry =je.toSqlAndBindAttribs2(new SqlContext(null, obj.getQuery()), profile);
result.addEntry(columnName, entry.first);
for(Variable binder: entry.second){
result.addField(binder);
}
} else if (value instanceof jef.database.Field) {// FBI
// Field不可能在此
String setColumn = meta.getColumnName((Field) value, profile, true);
result.addEntry(columnName, setColumn);
} else {
if(safeMerge && DbUtils.isInvalidValue(value, column, true)){
continue;
}
result.addEntry(columnName, new UpdateVairable(field));
}
}
return result;
}
@Override
UpdateClause toUpdateClause(IQueryableEntity obj, PartitionResult[] prs, boolean dynamic) throws SQLException {
return toUpdateClauseBatch(obj, prs, dynamic);
}
@Override
BindSql toWhereClause(JoinElement joinElement, SqlContext context, UpdateContext update, DatabaseDialect profile) {
return processor.toWhereClause(joinElement, context, update, profile, false);
}
}
/**
* 更新前,将所有LOB字段都移动到最后去。之前碰到一个BUG(Oracle)可能和驱动有关,LOB 不在最后,更新会出错。
*
* @param fields
* @param meta
*/
static void moveLobFieldsToLast(java.util.Map.Entry<Field, Object>[] fields, final ITableMetadata meta) {
Arrays.sort(fields, new Comparator<Map.Entry<Field, Object>>() {
public int compare(Map.Entry<Field, Object> o1, Map.Entry<Field, Object> o2) {
Field field1 = o1.getKey();
Field field2 = o2.getKey();
Assert.notNull(meta.getColumnDef(field1));
Assert.notNull(meta.getColumnDef(field2));
Class<? extends ColumnType> type1 = meta.getColumnDef(field1).get().getClass();
Class<? extends ColumnType> type2 = meta.getColumnDef(field2).get().getClass();
Boolean b1 = (type1 == ColumnType.Blob.class || type1 == ColumnType.Clob.class);
Boolean b2 = (type2 == ColumnType.Blob.class || type2 == ColumnType.Clob.class);
return b1.compareTo(b2);
}
});
}
/*
* 在更新数据的时候,如果无法确定哪些字段作了修改,那么就将所有非主键字段作为update的值
*/
@SuppressWarnings("unchecked")
static java.util.Map.Entry<Field, Object>[] getAllFieldValues(ITableMetadata meta, Map<Field, Object> map, BeanWrapper wrapper, DatabaseDialect profile) {
List<Entry<Field, Object>> result = new ArrayList<Entry<Field, Object>>();
for (ColumnMapping vType : meta.getColumns()) {
Field field = vType.field();
if (map.containsKey(field)) {
result.add(new Entry<Field, Object>(field, map.get(field)));
} else {
if (vType.isPk()) {
continue;
}
if (vType instanceof VersionSupportColumn) {
VersionSupportColumn timeColumn = (VersionSupportColumn) vType;
if (timeColumn.isUpdateAlways()) {
Object value = timeColumn.getAutoUpdateValue(profile, wrapper.getWrapped());
result.add(new Entry<Field, Object>(field, value));
continue;
}
}
result.add(new Entry<Field, Object>(field, wrapper.getPropertyValue(field.name())));
}
}
return result.toArray(new Map.Entry[result.size()]);
}
public int processUpdate(Session session, final IQueryableEntity obj, final UpdateClause updateClause, final BindSql whereClause, PartitionResult[] sites, long parseCost)
throws SQLException {
int total = 0;
long access = System.currentTimeMillis();
String dbName = null;
final SqlLog log = ORMConfig.getInstance().newLogger();
if (sites.length >= ORMConfig.getInstance().getParallelSelect()) {
List<DbTask> tasks = new ArrayList<DbTask>();
final AtomicInteger count = new AtomicInteger();
for (final PartitionResult site : sites) {
final OperateTarget db = session.selectTarget(site.getDatabase());
dbName = db.getTransactionId();
tasks.add(new DbTask() {
@Override
public void execute() throws SQLException {
count.addAndGet(processUpdate0(db, obj, updateClause, whereClause, site, log));
}
});
}
DbUtils.parallelExecute(tasks);
total = count.get();
} else {
for (PartitionResult site : sites) {
OperateTarget db = session.selectTarget(site.getDatabase());
dbName = db.getTransactionId();
total += processUpdate0(db, obj, updateClause, whereClause, site, log);
}
}
if (ORMConfig.getInstance().debugMode) {
access = System.currentTimeMillis() - access;
LogUtil.show(StringUtils.concat("Updated:", String.valueOf(total), "\t Time cost([ParseSQL]:", String.valueOf(parseCost), "ms, [DbAccess]:", String.valueOf(access),
"ms) |", dbName));
}
return total;
}
}