package nebula.data.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import nebula.data.SmartList;
import nebula.data.db.mapping.DbColumn;
import nebula.lang.Type;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
/**
* 同时多表持久化,实现中
* @author wanglocal
*
* @param <T>
*/
class DbComplexPersister<T> implements DbPersister<T> {
private final Log log;
final private Connection conn;
final private DbSqlHelper helper;
final BO2DBSerializer<T> serializer;
final private String SQL_GET;
final private String SQL_INSERT;
final private String SQL_UPDATE;
final private String SQL_DELETE;
final private String SQL_LIST;
final private String SQL_MAX_ID;
public DbComplexPersister(Connection conn, Type type, DbSqlHelper helper, BO2DBSerializer<T> serializer) {
super();
this.conn = conn;
this.helper = helper;
this.serializer = serializer;
log = LogFactory.getLog(DbComplexPersister.class + "_" + type.getName());
SQL_GET = helper.builderGet();
SQL_INSERT = helper.builderInsert();
SQL_UPDATE = helper.builderUpdate();
SQL_DELETE = helper.builderDelete();
SQL_LIST = helper.builderList();
SQL_MAX_ID = helper.builderMaxId();
if (!type.getAttrs().containsKey(Type.LEGACY)) {
this.ensureDBSchema();
}
}
private void ensureDBSchema() {
Statement statement = null;
boolean exist = false;
ResultSet rs = null;
try {
final SmartList<String, DbColumn> mapColumns = new SmartList<String, DbColumn>(new Function<DbColumn, String>() {
@Override
public String apply(DbColumn data) {
return data.columnName;
}
});
DbColumn[] userColumns = helper.getUserColumns();
for (int i = 0; i < userColumns.length; i++) {
mapColumns.add(userColumns[i]);
}
statement = conn.createStatement();
try {
rs = statement.executeQuery(helper.builderGetMeta());
exist = true;
} catch (SQLSyntaxErrorException e) {
// don't exist
}
if (exist) {
boolean isKeyChanged = false;
ResultSetMetaData metaData = rs.getMetaData();
int columnsSize = metaData.getColumnCount();
if (log.isTraceEnabled()) log.trace("\tcheck columns define");
ArrayList<String> needDeletedColumns = new ArrayList<String>();
Map<String, String> allColumns = new HashMap<String, String>();
ArrayList<DbColumn> typeNotMatchColumns = new ArrayList<DbColumn>();
ArrayList<DbColumn> typeSizeNotMatchColumns = new ArrayList<DbColumn>();
String curKeys = "";
for (int i = 1; i <= columnsSize; i++) {
String columnName = metaData.getColumnName(i);
DbColumn newColumn = mapColumns.get(columnName);
if (newColumn == null) {
if (log.isTraceEnabled())
log.trace("\t\t" + metaData.getColumnName(i) + "\t column type not exist in type or is system column \t- "
+ metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
if (!columnName.endsWith("_")) {
needDeletedColumns.add(columnName);
}
} else {
int size = metaData.getColumnDisplaySize(i);
int nullable = metaData.isNullable(i);
int precision = metaData.getPrecision(i);
int scale = metaData.getScale(i);
int jdbcType = metaData.getColumnType(i);
if (jdbcType != newColumn.jdbcType) {
if (log.isTraceEnabled())
log.trace("\t\t" + metaData.getColumnName(i) + "\t column type not match,need update \t- " + metaData.getColumnTypeName(i)
+ "\t" + metaData.getColumnDisplaySize(i));
typeNotMatchColumns.add(newColumn);
} else {
switch (jdbcType) {
case Types.DECIMAL:
if (newColumn.precision == precision && newColumn.scale == scale) {
} else if (newColumn.precision > precision && newColumn.scale > scale) {
if (log.isTraceEnabled())
log.trace("\t" + metaData.getColumnName(i) + "\t\t column size not match,need update \t- "
+ metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
typeSizeNotMatchColumns.add(newColumn);
} else if (newColumn.precision > precision || newColumn.scale > scale) {
int newPrecision = newColumn.precision > precision ? newColumn.precision : precision;
int newScale = newColumn.scale > scale ? newColumn.scale : scale;
typeSizeNotMatchColumns.add(new DbColumn(newColumn.fieldName, newColumn.columnName, newColumn.key, newColumn.nullable,
false, newColumn.rawType, newColumn.size, newPrecision, newScale));
if (log.isTraceEnabled())
log.trace("\t" + metaData.getColumnName(i) + "\t\tcolumn precision not match,need update \t- "
+ metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
break;
case Types.VARCHAR:
if (newColumn.size > size) typeSizeNotMatchColumns.add(newColumn);
break;
}
}
if (nullable == ResultSetMetaData.columnNoNulls) {
curKeys += columnName;
if (!newColumn.key) {
isKeyChanged = true;
}
}
allColumns.put(columnName, columnName);
}
}
rs.close();
conn.commit();
if (log.isTraceEnabled()) log.trace("\tcheck columns define finished");
if (log.isTraceEnabled()) log.trace("\tcheck key define");
StringBuffer newKeys = new StringBuffer();
ArrayList<DbColumn> notExistColumns = new ArrayList<DbColumn>();
for (DbColumn f : mapColumns) {
if (!allColumns.containsKey(f.columnName)) {
notExistColumns.add(f);
}
if (f.key) {
if (curKeys.indexOf(f.columnName) < 0) {
isKeyChanged = true;
}
if (newKeys.length() > 0) {
newKeys.append(",");
}
newKeys.append(f.columnName);
}
}
if (log.isTraceEnabled()) log.trace("\tcheck key define finished");
// key changed
if (isKeyChanged) {
statement = conn.createStatement();
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] key changed,reconstruct ");
statement.addBatch(helper.builderDrop());
statement.addBatch(helper.builderCreate());
statement.executeBatch();
log.trace("\t[" + helper.tableName + "] reconstruct finished table finished");
if (log.isTraceEnabled()) {
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] table layout after add not exist column");
rs = statement.executeQuery(helper.builderGetMeta());
metaData = rs.getMetaData();
columnsSize = metaData.getColumnCount();
for (int i = 1; i <= columnsSize; i++) {
log.trace("\t\t" + metaData.getColumnName(i) + "\t" + metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
rs.close();
}
} else {
// add not exist column to DB
if (notExistColumns.size() > 0) {
if (log.isTraceEnabled()) log.trace("\tadd not exist column");
statement = conn.createStatement();
for (DbColumn c : notExistColumns) {
log.trace("\t\t add column - " + helper.builderAddColumn(c));
statement.addBatch(helper.builderAddColumn(c));
}
statement.executeBatch();
if (log.isTraceEnabled()) log.trace("\tadd not exist column finished");
if (log.isTraceEnabled()) {
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] table layout after add not exist column");
rs = statement.executeQuery(helper.builderGetMeta());
metaData = rs.getMetaData();
columnsSize = metaData.getColumnCount();
for (int i = 1; i <= columnsSize; i++) {
log.trace("\t\t" + metaData.getColumnName(i) + "\t" + metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
rs.close();
}
}
// update don't match column to DB
if (typeNotMatchColumns.size() > 0) {
if (log.isTraceEnabled()) log.trace("\tupdate don't match column");
statement = conn.createStatement();
for (DbColumn c : typeNotMatchColumns) {
log.trace("\t\tupdate column - " + helper.builderModifyColumnDateType(c));
statement.addBatch(helper.builderRemoveColumn(c.columnName));
statement.addBatch(helper.builderAddColumn(c));
}
statement.executeBatch();
if (log.isTraceEnabled()) log.trace("\tupdate don't match column finished");
if (log.isTraceEnabled()) {
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] table layout after update don't match column");
rs = statement.executeQuery(helper.builderGetMeta());
metaData = rs.getMetaData();
columnsSize = metaData.getColumnCount();
for (int i = 1; i <= columnsSize; i++) {
log.trace("\t\t" + metaData.getColumnName(i) + "\t" + metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
rs.close();
}
}
// update size don't match column to DB
if (typeSizeNotMatchColumns.size() > 0) {
if (log.isTraceEnabled()) log.trace("\tupdate size don't match column");
statement = conn.createStatement();
for (DbColumn c : typeSizeNotMatchColumns) {
log.trace("\t\tmodify column - " + helper.builderModifyColumnDateType(c));
statement.addBatch(helper.builderModifyColumnDateType(c));
}
statement.executeBatch();
if (log.isTraceEnabled()) log.trace("\tupdate size don't match column finished");
if (log.isTraceEnabled()) {
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] table layout after update size don't match column");
rs = statement.executeQuery(helper.builderGetMeta());
metaData = rs.getMetaData();
columnsSize = metaData.getColumnCount();
for (int i = 1; i <= columnsSize; i++) {
log.debug("\t\t" + metaData.getColumnName(i) + "\t" + metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
rs.close();
}
}
// remove unused column
if (needDeletedColumns.size() > 0) {
if (log.isTraceEnabled()) log.trace("\tremove unused column");
statement = conn.createStatement();
for (String key : needDeletedColumns) {
log.trace("\t\tremove column - " + helper.builderRemoveColumn(key));
statement.addBatch(helper.builderRemoveColumn(key));
}
statement.executeBatch();
if (log.isTraceEnabled()) log.trace("\tremove unused column finished");
if (log.isTraceEnabled()) {
if (log.isTraceEnabled()) log.trace("\t[" + helper.tableName + "] table layout after remove unused column");
rs = statement.executeQuery(helper.builderGetMeta());
metaData = rs.getMetaData();
columnsSize = metaData.getColumnCount();
for (int i = 1; i <= columnsSize; i++) {
log.trace("\t\t" + metaData.getColumnName(i) + "\t" + metaData.getColumnTypeName(i) + "\t" + metaData.getColumnDisplaySize(i));
}
rs.close();
}
}
}
conn.commit();
} else {
statement.executeUpdate(helper.builderCreate());
conn.commit();
}
} catch (SQLException e) {
log.trace(e);
throw new RuntimeException(e);
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
log.error(e.getClass().getName(), e);
throw new RuntimeException(e);
}
}
}
public T get(Object... keys) {
if (log.isTraceEnabled()) log.trace("\tSQL_GET : " + Joiner.on(',').join(keys));
List<T> list = executeQuery(SQL_GET, keys);
if (list == null) {
throw new RuntimeException("Can not find record key:" + keys);
}
if (list.size() != 1) {
throw new RuntimeException("Can not find record key:" + keys);
}
return list.get(0);
}
@Override
public void insert(T value) {
log.debug("\tSQL_INSERT : " + value);
executeUpdate(SQL_INSERT, value);
}
@Override
public void update(T value, Object... keys) {
log.debug("SQL_UPDATE - " + value);
executeUpdate(SQL_UPDATE, value, keys);
}
@Override
public void deleteAll() {
try {
conn.createStatement().execute(helper.builderDeleteAll());
} catch (Exception e) {
log.error(e.getClass().getName(), e);
throw new RuntimeException(e);
}
}
@Override
public List<T> getAll() {
return executeQuery(SQL_LIST); // Only hot data
}
@Override
public void delete(T value) {
log.debug(SQL_DELETE + " : " + value);
executeUpdate(SQL_DELETE, value);
}
@Override
public void drop() {
try {
conn.createStatement().execute(helper.builderDrop());
} catch (Exception e) {
log.error(e.getClass().getName(), e);
}
}
@Override
public void close() {
}
@Override
public long getCurrentMaxID() {
ResultSet res = null;
try {
res = conn.createStatement().executeQuery(SQL_MAX_ID);
if (log.isTraceEnabled()) {
log.trace("\texecuteQuery Open Recordset");
}
while (res.next()) {
return res.getLong(1);
}
return 0;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (res != null) {
res.close();
if (log.isTraceEnabled()) {
log.trace("\texecuteQuery Close Recordset");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private List<T> executeQuery(String sql, Object... keys) {
ResultSet res = null;
try {
PreparedStatement pstmt = conn.prepareStatement(sql);
int pos = 0;
for (int i = 0; i < keys.length; i++) {
pstmt.setObject(pos + i + 1, keys[i]);
}
res = pstmt.executeQuery();
if (log.isTraceEnabled()) {
log.trace("\texecuteQuery Open Recordset");
}
List<T> list = new ArrayList<T>();
while (res.next()) {
T v = serializer.fromDb(res);
list.add(v);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (res != null) {
res.close();
if (log.isTraceEnabled()) {
log.trace("\texecuteQuery Close Recordset");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private void executeUpdate(String sql, T v, Object... keys) {
ResultSet res = null;
try {
PreparedStatement pstmt = conn.prepareStatement(sql);
int pos = serializer.toDb(pstmt, v);
for (int i = 0; i < keys.length; i++) {
pstmt.setObject(pos + i, keys[i]);
}
pstmt.executeUpdate();
conn.commit();
pstmt.clearParameters();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (res != null) res.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}